From aaf2784a9a0f9a2cc381fe22fb3eeed499019ca1 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Tue, 4 Aug 2026 15:12:36 +0200 Subject: [PATCH] chore: Clean up dump.rdb, templates/, test_report.md; update .a0 status files --- .a0/current_status.md | 70 +++---- .a0/next_steps.md | 21 ++- frontend/test_report.md | 44 ----- templates/base.html | 58 ------ templates/company_form.html | 20 -- templates/contact_form.html | 20 -- templates/contact_list.html | 37 ---- templates/dashboard.html | 34 ---- templates/login.html | 12 -- templates/plugin-template/README.md | 60 ------ templates/plugin-template/__init__.py | 1 - .../migrations/0001_initial.sql | 17 -- templates/plugin-template/models.py | 38 ---- templates/plugin-template/plugin.py | 178 ------------------ templates/plugin-template/routes.py | 48 ----- templates/plugin-template/schemas.py | 50 ----- templates/plugin-template/services.py | 39 ---- templates/plugin-template/tests/__init__.py | 1 - .../plugin-template/tests/test_plugin.py | 63 ------- templates/register.html | 12 -- 20 files changed, 41 insertions(+), 782 deletions(-) delete mode 100644 frontend/test_report.md delete mode 100644 templates/base.html delete mode 100644 templates/company_form.html delete mode 100644 templates/contact_form.html delete mode 100644 templates/contact_list.html delete mode 100644 templates/dashboard.html delete mode 100644 templates/login.html delete mode 100644 templates/plugin-template/README.md delete mode 100644 templates/plugin-template/__init__.py delete mode 100644 templates/plugin-template/migrations/0001_initial.sql delete mode 100644 templates/plugin-template/models.py delete mode 100644 templates/plugin-template/plugin.py delete mode 100644 templates/plugin-template/routes.py delete mode 100644 templates/plugin-template/schemas.py delete mode 100644 templates/plugin-template/services.py delete mode 100644 templates/plugin-template/tests/__init__.py delete mode 100644 templates/plugin-template/tests/test_plugin.py delete mode 100644 templates/register.html diff --git a/.a0/current_status.md b/.a0/current_status.md index 3b48b5c..46e804c 100644 --- a/.a0/current_status.md +++ b/.a0/current_status.md @@ -1,47 +1,31 @@ # LeoCRM — Current Status -**Phase**: Fix Branch — 20/22 FIX-PLAN Items erledigt -**Last update**: 2026-07-26 16:25 -**Branch**: main (leocrm-fix) +**Last update**: 2026-08-04 +**Branch**: main +**Git HEAD**: 157e454 +**Alembic-Head**: 0103 +**Produktion**: https://crm.media-on.de — healthy -## FIX-PLAN Überprüfung (2026-07-26) -Alle 22 Items gegen Codebasis verifiziert. 20 erledigt, 2 offen. +## Security Fix Plan — Alle Phasen abgeschlossen +| Phase | Status | +|-------|--------| +| 1 — Kritische Sicherheitslücken | ✅ | +| 2 — Visibility Filter & Owner ID | ✅ | +| 3 — WebSocket CSRF, SameSite, FK CASCADE | ✅ | +| 4 — Krisensicherheit (Circuit Breaker, Retry, Fallback) | ✅ | +| 5 — Architektur-Lücken (9 Sub-Tasks) | ✅ | -### Erledigt (20) -- P0-1: Auth-Bypass entfernt ✅ -- P0-2: Migrationen repariert ✅ -- P0-3: Plugin-Upload deaktiviert ✅ -- P0-4: RLS FORCE + WITH CHECK ✅ -- P0-5: Plugin-Doppelregistrierung behoben ✅ -- P0-6: Persistent Volume ✅ -- P1-1: User/Tenant-Modell bereinigt ✅ -- P1-2: Redis zentralisiert ✅ -- P1-3: Worker ausgelagert ✅ -- P1-4: Transactional Outbox ✅ -- P1-5: XSS-Stellen geschlossen ✅ -- P1-6: DMS lastfest ✅ -- P1-7: Permission-System vereinheitlicht ✅ -- P1-8: Password Reset funktionsfähig ✅ -- P1-9: Metrics abgesichert ✅ -- P1-10: Coolify-Doku & Config korrigiert ✅ -- P1-11: Cross-Tenant FK ✅ -- P2-1: Contact Model normalisiert ✅ -- P2-3: Commands & Statusmaschinen ✅ -- P2-4: SPA Path-Traversal ✅ +## Implementierte Features (Phase 5) +- PWA (VitePWA, manifest.json, service worker) +- Public Plugin Endpoints (is_public, share-link routes) +- Contacts Embedding (Vector(768), HNSW index) +- 10 Search Providers (contact, company, mail, file, event, task, contactperson, tag, conversation, user) +- Plugin-Marketplace (listing, download, Ed25519 verify, install) +- Agent Memory (persistent, pgvector semantic search) +- GraphRAG (entity relationships, BFS traversal, search provider) +- Subagents/Multi-Agent (AgentCoordinator, subtask management) +- External Agent API (Bearer token, SSE streaming, rate limiting) +- Circuit Breaker + DB Retry + Redis Graceful Degradation -### Offen (2) -- P0-7: App von öffentlicher Domain nehmen (operational — 30 Min) -- P2-2: Plugin-Cross-Imports reduzieren (228 Imports — 1-2 Wochen) - -## Previous: P1-4: Transactional Outbox — COMPLETE -- Migration 0040_outbox.py created (down_revision=0039_contact_normalize) -- event_outbox table: id, tenant_id, event_name, payload JSONB, status, attempts, max_attempts, next_retry_at, timestamps -- app/core/outbox.py: enqueue_outbox_event() + process_outbox_batch() with FOR UPDATE SKIP LOCKED, exponential backoff retry -- app/core/event_bus.py: added publish_with_results() for error-aware publishing; docstring note about outbox -- app/core/worker.py: process_outbox_job cron (every 5s, Redis distributed lock) -- app/services/contact_service.py: contact.created, lead.created, contact.updated → enqueue_outbox_event -- app/models/outbox.py: SQLAlchemy ORM model for event_outbox -- tests/test_outbox.py: 6 tests, all passing -- py_compile: OK, alembic heads: single head 0040_outbox - -## Previous: P2-1: Unified Contact Model normalisieren — COMPLETE -- Migration 0039_contact_normalize.py (down_revision=0038_dms_content_hash) +## Offene Items +- IMPLEMENTATION_PLAN.md: 14 Frontend-Features in 4 Phasen (nicht begonnen) +- Test-Instanzen CRM2/CRM3 noch aktiv (können gelöscht werden) diff --git a/.a0/next_steps.md b/.a0/next_steps.md index b6a465e..ee05459 100644 --- a/.a0/next_steps.md +++ b/.a0/next_steps.md @@ -1,10 +1,17 @@ # LeoCRM — Next Steps -## FIX-PLAN Offene Items (2026-07-26) -1. P0-7: App von öffentlicher Domain nehmen (operational — 30 Min) -2. P2-2: Plugin-Cross-Imports reduzieren (228 Imports — 1-2 Wochen) +## Offene Items (2026-08-04) +1. IMPLEMENTATION_PLAN.md — 14 Frontend-Features in 4 Phasen + - Phase 1: Workflows UI, Dedup/Merge UI, Import/Export UI, Print/PDF + - Phase 2: Tags UI, Custom Fields UI, Notifications Dropdown + - Phase 3: Saved Filters, Entity History, Activity Timeline, API Docs + - Phase 4: Webhooks, Backup/Restore UI, Onboarding +2. Test-Instanzen CRM2/CRM3 können gelöscht werden +3. Tests für neue Plugins (agent_memory, graph_rag, marketplace) schreiben -## Abgeschlossen -- P2-1: Unified Contact Model normalisieren — COMPLETE -- P1-4: Transactional Outbox — COMPLETE -- 20/22 FIX-PLAN Items erledigt (siehe .a0/current_status.md) +## Abgeschlossen (2026-08-04) +- Security Fix Plan Phase 1-5 komplett +- Alle alten Plan-Dateien gelöscht (Sanierungsplan, FIX-PLAN, etc.) +- 3 neue Plugins: agent_memory, graph_rag, marketplace +- Resilience-Features: Circuit Breaker, DB Retry, Redis Fallback +- PWA, Public Endpoints, Search Coverage (10 providers) diff --git a/frontend/test_report.md b/frontend/test_report.md deleted file mode 100644 index 5ad6d03..0000000 --- a/frontend/test_report.md +++ /dev/null @@ -1,44 +0,0 @@ -# Test Report — Phase 4: Unified Messaging Frontend - -## Task -Implement MessageSidebar replacing AISidebar with comm API backend integration. - -## Files Created/Modified - -### New Files -1. `src/store/commStore.ts` — Zustand store for conversations, messages, typing, unread counts -2. `src/api/comm.ts` — API client covering all `/api/v1/comm/*` endpoints (conversations, messages, reactions, read state, mini-apps, block types) -3. `src/hooks/useCommWebSocket.ts` — WebSocket hook with auto-reconnect, ping, streaming, typing indicators -4. `src/components/layout/MessageSidebar.tsx` — Main sidebar component (716 lines) replicating AISidebar design exactly - -### Modified Files -5. `src/store/uiStore.ts` — Added `messageSidebarCollapsed` + `toggleMessageSidebar`; replaced mock notifications with empty array; kept `aiSidebarCollapsed` for backward compat -6. `src/components/layout/AppShell.tsx` — Replaced `AISidebar` import with `MessageSidebar` -7. `src/components/layout/TopBar.tsx` — Replaced `openAISidebarProactive` with `toggleMessageSidebar` - -## TypeScript Compilation - -Command: `npx tsc --noEmit` - -``` -src/pages/Dms.tsx(553,13): error TS2322: ... onRangeSelect does not exist on type FileExplorerProps -src/pages/Dms.tsx(647,15): error TS2322: ... onRangeSelect does not exist on type FileExplorerProps -``` - -**Result:** All new/modified files compile cleanly. Only 2 pre-existing errors in `Dms.tsx` (unrelated to this task — `onRangeSelect` prop missing on `FileExplorerProps` type). - -## Smoke Test Description - -- **Build:** `npx tsc --noEmit` exits with only pre-existing Dms.tsx errors (0 new errors from this task) -- **Design fidelity:** MessageSidebar uses identical icons (robotIcon, bellIcon, bulbIcon, teamIcon, chatBubbleIcon), identical ResizablePanel (320px initial, 240-600 range, handleSide=left), identical mobile/desktop structure (collapsed icon strip → expanded resizable panel + mobile overlay) -- **API integration:** comm.ts uses same `apiGet/apiPost/apiPatch/apiDelete` from `client.ts` as `ai.ts` — consistent with existing conventions -- **WebSocket:** Auto-reconnect with exponential backoff, ping interval, handles message.new, conversation.updated, message.streaming, typing events -- **Store:** commStore provides conversations, messages per conversation, typing users, unread counts with all setter methods -- **uiStore:** messageSidebarCollapsed defaults to true (same as aiSidebarCollapsed), toggleMessageSidebar works, mock notifications replaced with empty array - -## What Was NOT Tested - -- Live API calls (requires running backend with kommunikation plugin) -- WebSocket connection (requires authenticated session) -- Visual rendering (requires dev server + browser) -- These require runtime verification in a subsequent phase diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index 27dad78..0000000 --- a/templates/base.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - LeoCRM - - - - -
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} - {% block content %}{% endblock %} -
- - diff --git a/templates/company_form.html b/templates/company_form.html deleted file mode 100644 index acf5e22..0000000 --- a/templates/company_form.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

{% if company %}Firma bearbeiten{% else %}Neue Firma{% endif %}

-
- - - - - - - - - - - - - - Abbrechen -
-{% endblock %} diff --git a/templates/contact_form.html b/templates/contact_form.html deleted file mode 100644 index 68bb1c7..0000000 --- a/templates/contact_form.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

{% if contact %}Kontakt bearbeiten{% else %}Neuer Kontakt{% endif %} – {{ company.name }}

-
- - - - - - - - - - - - - - Abbrechen -
-{% endblock %} diff --git a/templates/contact_list.html b/templates/contact_list.html deleted file mode 100644 index 4a31326..0000000 --- a/templates/contact_list.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

{{ company.name }} – Kontaktpersonen

-Neuer Kontakt -Zurück -{% if contacts %} - - - - - - - - - - - - {% for c in contacts %} - - - - - - - - {% endfor %} - -
NamePositionEmailTelefonAktionen
{{ c.first_name }} {{ c.last_name }}{{ c.position or '-' }}{{ c.email or '-' }}{{ c.phone or '-' }} - Edit -
- -
-
-{% else %} -

Keine Kontaktpersonen erfasst.

-{% endif %} -{% endblock %} diff --git a/templates/dashboard.html b/templates/dashboard.html deleted file mode 100644 index 3062976..0000000 --- a/templates/dashboard.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Dashboard

-Neue Firma -{% if companies %} - - - - - - - - - - - {% for c in companies %} - - - - - - - {% endfor %} - -
NameTelefonEmailAktionen
{{ c.name }}{{ c.phone or '-' }}{{ c.email or '-' }} - Edit -
- -
-
-{% else %} -

Noch keine Firmen erfasst.

-{% endif %} -{% endblock %} diff --git a/templates/login.html b/templates/login.html deleted file mode 100644 index 359c936..0000000 --- a/templates/login.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Login

-
- - - - - -
-

Noch kein Konto? Registrieren

-{% endblock %} diff --git a/templates/plugin-template/README.md b/templates/plugin-template/README.md deleted file mode 100644 index 7530578..0000000 --- a/templates/plugin-template/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Plugin Template - -A minimal plugin template for LeoCRM. Copy this directory to `app/plugins/builtins//` and customize. - -## Quick Start - -1. **Copy the template:** - ```bash - cp -r templates/plugin-template app/plugins/builtins/my_plugin - ``` - -2. **Rename the plugin class:** - Edit `plugin.py` and rename `ExamplePlugin` to your plugin name. - -3. **Update the manifest:** - - Change `name`, `version`, `display_name`, `description` - - Add your routes, events, migrations, permissions - - Add UI contributions (menu items, page routes, etc.) - -4. **Implement routes:** - Edit `routes.py` with your API endpoints. - -5. **Add database models:** - Uncomment and customize `models.py`. - -6. **Write migrations:** - Add SQL migration files to `migrations/`. - -7. **Write tests:** - Add tests to `tests/test_plugin.py`. - -## Directory Structure - -``` -templates/plugin-template/ -├── __init__.py # Package init -├── plugin.py # Plugin class with manifest (required) -├── routes.py # FastAPI route definitions -├── models.py # SQLAlchemy models (optional, commented out) -├── schemas.py # Pydantic schemas (optional) -├── services.py # Business logic (optional) -├── migrations/ # SQL migration files -│ └── 0001_initial.sql -├── tests/ # Plugin tests -│ ├── __init__.py -│ └── test_plugin.py -└── README.md # This file -``` - -## Manifest Fields - -See `docs/plugin-development-guide.md` for a complete reference of all manifest fields. - -## Key Points - -- All API routes must be secured with `require_permission` -- Event handlers are named `on_` with dots replaced by underscores -- Migration files run in alphanumeric order -- UI component paths use the `@/` alias (resolved to `src/` by Vite) -- i18n keys should be prefixed with the plugin name diff --git a/templates/plugin-template/__init__.py b/templates/plugin-template/__init__.py deleted file mode 100644 index 9765232..0000000 --- a/templates/plugin-template/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Plugin template package.""" diff --git a/templates/plugin-template/migrations/0001_initial.sql b/templates/plugin-template/migrations/0001_initial.sql deleted file mode 100644 index 06bf433..0000000 --- a/templates/plugin-template/migrations/0001_initial.sql +++ /dev/null @@ -1,17 +0,0 @@ --- 0001_initial.sql --- Initial migration for the example plugin. --- Creates the example_items table. - -CREATE TABLE IF NOT EXISTS example_items ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - name VARCHAR(200) NOT NULL, - description TEXT, - config JSONB DEFAULT '{}', - is_active BOOLEAN DEFAULT true, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_example_items_tenant_id ON example_items(tenant_id); -CREATE INDEX IF NOT EXISTS idx_example_items_name ON example_items(name); diff --git a/templates/plugin-template/models.py b/templates/plugin-template/models.py deleted file mode 100644 index d62a1cb..0000000 --- a/templates/plugin-template/models.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -SQLAlchemy models for the example plugin. - -Uncomment and customize for your plugin's data model. -""" - -# from __future__ import annotations -# -# import uuid -# from datetime import datetime -# -# from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, Text -# from sqlalchemy.dialects.postgresql import UUID -# from sqlalchemy.orm import Mapped, mapped_column, relationship -# -# from app.database import Base -# -# -# class ExampleItem(Base): -# __tablename__ = "example_items" -# -# id: Mapped[uuid.UUID] = mapped_column( -# UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 -# ) -# tenant_id: Mapped[uuid.UUID] = mapped_column( -# UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=False -# ) -# name: Mapped[str] = mapped_column(String(200), nullable=False) -# description: Mapped[str | None] = mapped_column(Text, nullable=True) -# is_active: Mapped[bool] = mapped_column(Boolean, default=True) -# created_at: Mapped[datetime] = mapped_column( -# DateTime(timezone=True), default=datetime.utcnow -# ) -# updated_at: Mapped[datetime] = mapped_column( -# DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow -# ) -# -# tenant = relationship("Tenant", back_populates="example_items") diff --git a/templates/plugin-template/plugin.py b/templates/plugin-template/plugin.py deleted file mode 100644 index c64928e..0000000 --- a/templates/plugin-template/plugin.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -Plugin Template — Example plugin demonstrating all manifest fields. - -Copy this directory to app/plugins/builtins// and customize. -""" - -from __future__ import annotations - -from app.plugins.base import BasePlugin -from app.plugins.manifest import ( - PluginManifest, - PluginRouteDef, - FieldDefinition, - FrontendMenuItem, - FrontendPageRoute, - FrontendDetailTab, - FrontendSettingsPage, - FrontendDashboardWidget, -) - - -class ExamplePlugin(BasePlugin): - """ - Example plugin demonstrating all manifest fields. - - Remove or comment out fields you don't need. - """ - - manifest = PluginManifest( - # ── Core Metadata ────────────────────────────────────────────── - name="example_plugin", - version="1.0.0", - display_name="Example Plugin", - description="A minimal example plugin demonstrating all manifest fields.", - dependencies=[], - is_core=False, - # ── API Routes ───────────────────────────────────────────────── - routes=[ - PluginRouteDef( - path="/api/v1/example", - module="app.plugins.builtins.example.routes", - router_attr="router", - ), - ], - # ── Event Subscriptions ──────────────────────────────────────── - events=[ - "contact.created", - "contact.updated", - ], - # ── Database Migrations ──────────────────────────────────────── - migrations=[ - "0001_initial.sql", - ], - # ── RBAC Permissions ─────────────────────────────────────────── - permissions=[ - "example:read", - "example:write", - ], - # ── Field Definitions (Field-Level Permissions) ──────────────── - field_definitions=[ - FieldDefinition( - module="contacts", - field="custom_field", - label="Custom Field", - sensitivity="normal", - ), - ], - # ── AI Agent Capabilities ────────────────────────────────────── - agent_capabilities=[ - "example:search", - ], - # ── Frontend UI: Sidebar Menu Items ──────────────────────────── - menu_items=[ - FrontendMenuItem( - label_key="nav.example", - label="Example", - path="/example", - icon="Sparkles", - order=100, - ), - ], - # ── Frontend UI: Page Routes ─────────────────────────────────── - page_routes=[ - FrontendPageRoute( - path="/example", - component="@/pages/Example", - protected=True, - ), - ], - # ── Frontend UI: Detail Tabs ─────────────────────────────────── - detail_tabs=[ - FrontendDetailTab( - entity_type="contact", - label_key="tabs.example", - label="Example", - component="@/components/ExampleTab", - icon="Sparkles", - order=50, - permission="example:read", - ), - ], - # ── Frontend UI: Settings Pages ──────────────────────────────── - settings_pages=[ - FrontendSettingsPage( - path="example", - label_key="settings.example", - label="Example", - component="@/pages/ExampleSettings", - icon="Sparkles", - order=100, - permission="example:write", - ), - ], - # ── Frontend UI: Dashboard Widgets ───────────────────────────── - dashboard_widgets=[ - FrontendDashboardWidget( - id="example_stats", - label_key="widgets.example", - label="Example Stats", - component="@/components/ExampleWidget", - icon="LayoutDashboard", - order=100, - col_span=2, - row_span=1, - permission="example:read", - ), - ], - ) - - # ─── Lifecycle Hooks ─────────────────────────────────────────────── - - async def on_install(self, db, service_container): - """Called after migrations are run.""" - # Perform seed data or initial setup here - pass - - async def on_activate(self, db, service_container, event_bus): - """Called when the plugin is activated.""" - # Default implementation subscribes to manifest events - await super().on_activate(db, service_container, event_bus) - - async def on_deactivate(self, db, service_container, event_bus): - """Called when the plugin is deactivated.""" - # Default implementation unsubscribes all event listeners - await super().on_deactivate(db, service_container, event_bus) - - async def on_uninstall(self, db, service_container): - """Called before data tables are dropped.""" - # Clean up external resources here - pass - - # ─── Event Handlers ──────────────────────────────────────────────── - - async def on_contact_created(self, event_data: dict) -> None: - """Handle contact.created event.""" - contact_id = event_data.get("contact_id") - # React to new contact - pass - - async def on_contact_updated(self, event_data: dict) -> None: - """Handle contact.updated event.""" - contact_id = event_data.get("contact_id") - # React to contact update - pass - - # ─── Notification Types ──────────────────────────────────────────── - - def get_notification_types(self) -> list[dict]: - """Return notification types this plugin registers.""" - return [ - { - "type_key": "example.notification", - "label": "Example Notification", - "category": "general", - "description": "Notification from the example plugin", - "is_enabled_by_default": True, - }, - ] diff --git a/templates/plugin-template/routes.py b/templates/plugin-template/routes.py deleted file mode 100644 index 9000235..0000000 --- a/templates/plugin-template/routes.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -API routes for the example plugin. - -Each route must be secured with require_permission. -""" - -from fastapi import APIRouter, Depends - -from app.deps import get_current_user, require_permission - -router = APIRouter() - - -@router.get( - "", - dependencies=[Depends(require_permission("example:read"))], -) -async def list_items(current_user: dict = Depends(get_current_user)): - """List all items.""" - return {"items": [], "total": 0} - - -@router.post( - "", - status_code=201, - dependencies=[Depends(require_permission("example:write"))], -) -async def create_item(current_user: dict = Depends(get_current_user)): - """Create a new item.""" - return {"status": "created"} - - -@router.get( - "/{item_id}", - dependencies=[Depends(require_permission("example:read"))], -) -async def get_item(item_id: str, current_user: dict = Depends(get_current_user)): - """Get a single item by ID.""" - return {"id": item_id} - - -@router.delete( - "/{item_id}", - dependencies=[Depends(require_permission("example:write"))], -) -async def delete_item(item_id: str, current_user: dict = Depends(get_current_user)): - """Delete an item by ID.""" - return {"status": "deleted"} diff --git a/templates/plugin-template/schemas.py b/templates/plugin-template/schemas.py deleted file mode 100644 index adde78a..0000000 --- a/templates/plugin-template/schemas.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Pydantic schemas for the example plugin. - -Customize these for your plugin's API request/response models. -""" - -from __future__ import annotations - -from datetime import datetime -from uuid import UUID - -from pydantic import BaseModel, Field - - -class ExampleItemBase(BaseModel): - """Base schema for an example item.""" - - name: str = Field(..., min_length=1, max_length=200, description="Item name") - description: str | None = Field(None, max_length=1000, description="Item description") - - -class ExampleItemCreate(ExampleItemBase): - """Schema for creating an example item.""" - pass - - -class ExampleItemUpdate(BaseModel): - """Schema for updating an example item.""" - - name: str | None = Field(None, min_length=1, max_length=200) - description: str | None = Field(None, max_length=1000) - - -class ExampleItemResponse(ExampleItemBase): - """Schema for returning an example item.""" - - id: UUID - tenant_id: UUID - is_active: bool - created_at: datetime - updated_at: datetime - - model_config = {"from_attributes": True} - - -class ExampleItemListResponse(BaseModel): - """Schema for a paginated list of example items.""" - - items: list[ExampleItemResponse] - total: int diff --git a/templates/plugin-template/services.py b/templates/plugin-template/services.py deleted file mode 100644 index 22c2132..0000000 --- a/templates/plugin-template/services.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Business logic services for the example plugin. - -Customize these for your plugin's business logic. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from sqlalchemy.ext.asyncio import AsyncSession - - -class ExampleService: - """Service class for example plugin business logic.""" - - def __init__(self, db: AsyncSession): - self.db = db - - async def list_items(self, tenant_id: str, skip: int = 0, limit: int = 100) -> dict: - """List items for a tenant.""" - # TODO: Implement with actual database queries - return {"items": [], "total": 0} - - async def create_item(self, tenant_id: str, data: dict) -> dict: - """Create a new item.""" - # TODO: Implement with actual database operations - return {"id": "new-uuid", **data} - - async def get_item(self, item_id: str, tenant_id: str) -> dict | None: - """Get a single item by ID.""" - # TODO: Implement with actual database queries - return None - - async def delete_item(self, item_id: str, tenant_id: str) -> bool: - """Delete an item by ID.""" - # TODO: Implement with actual database operations - return True diff --git a/templates/plugin-template/tests/__init__.py b/templates/plugin-template/tests/__init__.py deleted file mode 100644 index 25f2c51..0000000 --- a/templates/plugin-template/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the example plugin.""" diff --git a/templates/plugin-template/tests/test_plugin.py b/templates/plugin-template/tests/test_plugin.py deleted file mode 100644 index a754a51..0000000 --- a/templates/plugin-template/tests/test_plugin.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Tests for the example plugin.""" - -import pytest -from httpx import AsyncClient, ASGITransport - -from app.main import create_app - - -@pytest.fixture -async def client(): - """Create a test client.""" - app = create_app() - transport = ASGITransport(app=app) - async with AsyncClient(transport=transport, base_url="http://test") as ac: - yield ac - - -@pytest.fixture -def auth_headers(): - """Return headers with valid authentication.""" - return { - "Authorization": "Bearer test-token", - "X-Tenant-ID": "test-tenant", - } - - -@pytest.mark.asyncio -async def test_list_items_requires_auth(client: AsyncClient): - """Test that listing items requires authentication.""" - response = await client.get("/api/v1/example") - assert response.status_code in (401, 403) - - -@pytest.mark.asyncio -async def test_list_items_with_auth(client: AsyncClient, auth_headers: dict): - """Test that listing items works with authentication.""" - response = await client.get("/api/v1/example", headers=auth_headers) - assert response.status_code == 200 - data = response.json() - assert "items" in data - assert "total" in data - - -@pytest.mark.asyncio -async def test_create_item_requires_write_permission(client: AsyncClient, auth_headers: dict): - """Test that creating items requires write permission.""" - response = await client.post( - "/api/v1/example", - headers=auth_headers, - json={"name": "Test Item"}, - ) - # May return 201 or 403 depending on test permissions - assert response.status_code in (201, 403) - - -@pytest.mark.asyncio -async def test_get_item_returns_404_for_missing(client: AsyncClient, auth_headers: dict): - """Test that getting a non-existent item returns 404.""" - response = await client.get( - "/api/v1/example/non-existent-id", - headers=auth_headers, - ) - assert response.status_code == 404 diff --git a/templates/register.html b/templates/register.html deleted file mode 100644 index 7491a52..0000000 --- a/templates/register.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Registrieren

-
- - - - - -
-

Bereits registriert? Anmelden

-{% endblock %}