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 %}
-
-{% 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 }}
-
-{% 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 %}
-
-
-
- | Name |
- Position |
- Email |
- Telefon |
- Aktionen |
-
-
-
- {% for c in contacts %}
-
- | {{ c.first_name }} {{ c.last_name }} |
- {{ c.position or '-' }} |
- {{ c.email or '-' }} |
- {{ c.phone or '-' }} |
-
- Edit
-
- |
-
- {% endfor %}
-
-
-{% 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 %}
-
-
-
- | Name |
- Telefon |
- Email |
- Aktionen |
-
-
-
- {% for c in companies %}
-
- | {{ c.name }} |
- {{ c.phone or '-' }} |
- {{ c.email or '-' }} |
-
- Edit
-
- |
-
- {% endfor %}
-
-
-{% 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 %}