Files
leocrm-bot dd16940bb2 T02: companies + contacts + import/export + N:M + soft-delete + GDPR + FTS
- Company CRUD with soft-delete, FTS search (tsvector + GIN), filter, pagination
- Contact CRUD with N:M company linking via company_contacts
- CSV/XLSX export, CSV import with dry-run preview
- GDPR hard-delete with deletion_log
- Audit log on all mutations
- 27 new tests (24 ACs), 56 total tests pass
- Migration 0002: contacts, company_contacts, FTS search_tsv
- Fixed T01 tests: POST→201, PATCH→PUT compatibility
2026-06-29 08:02:15 +02:00

132 lines
6.3 KiB
Markdown

# T03: Plugin System Framework
## Context
- Project: LeoCRM (greenfield rewrite, Option C)
- Repo: /a0/usr/workdir/dev-projects/leocrm
- T01 COMPLETE: auth, multi-tenant, RBAC, sessions, audit, notifications
- T01 commit: 7a7daf8 (pushed to Forgejo)
- Tech: FastAPI + SQLAlchemy 2.0 async + PostgreSQL + Redis + Pydantic v2
## Existing T01 Code to Build On
- `app/core/event_bus.py` — Event bus (0% coverage, needs integration)
- `app/core/service_container.py` — DI container (0% coverage, needs integration)
- `app/core/db/__init__.py` — Engine, Session, Base, TenantMixin, set_tenant_context
- `app/deps.py` — get_current_user, require_admin, get_tenant_id
- `app/core/audit.py` — log_audit function
- `app/main.py` — create_app factory, mounts routers, middleware
- `app/routes/__init__.py` — router aggregator
- `tests/conftest.py` — Test fixtures with TRUNCATE CASCADE
## Requirements (7)
F-PLUGIN-01: Plugin-System für Module — Module als Plugins, Daten austauschbar
F-PLUGIN-02: Plugin-Schnittstellen-Definition — API-Contract, Lifecycle-Hooks, Manifest, Abhängigkeiten
F-CORE-01: Multi-Tenant-Architektur
F-CORE-03: RBAC
F-CORE-04: Audit-Log
F-CORE-05: Event-System
F-TEST-01: Test-Coverage
## Acceptance Criteria (14)
1. GET /api/v1/plugins → 200 + list of plugins with status
2. POST /api/v1/plugins/{name}/install → 200, plugin status=installed, migrations run
3. POST /api/v1/plugins/{name}/activate → 200, plugin status=active, routes registered
4. POST /api/v1/plugins/{name}/deactivate → 200, plugin status=inactive, routes unregistered
5. DELETE /api/v1/plugins/{name} → 200, plugin removed
6. DELETE /api/v1/plugins/{name}?remove_data=true → 200, plugin tables dropped
7. GET /api/v1/plugins/manifest → 200 + manifest schema documentation
8. Plugin activation registers event listeners on event bus
9. Plugin deactivation unregisters event listeners
10. Plugin migration creates tables with tenant_id column
11. Plugin migration validator rejects tables without tenant_id
12. Plugin DB migrations tracked in plugin_migrations table
13. Activating already-active plugin → idempotent (200, no error)
14. Deactivating inactive plugin → idempotent (200)
## Files to Create/Modify
### New Files:
- `app/models/plugin.py` — Plugin, PluginMigration (plugin_migrations tracking table)
- `app/schemas/plugin.py` — Plugin schemas (manifest, status, install/activate response)
- `app/services/plugin_service.py` — Plugin lifecycle: discover, install, activate, deactivate, uninstall
- `app/routes/plugins.py` — Plugin endpoints (list/install/activate/deactivate/uninstall/manifest)
- `app/plugins/__init__.py` — Plugin package init
- `app/plugins/base.py` — BasePlugin abstract class with lifecycle hooks
- `app/plugins/manifest.py` — PluginManifest Pydantic schema (name, version, dependencies, routes, events, migrations)
- `app/plugins/registry.py` — Plugin registry (in-memory + DB-backed status)
- `app/plugins/migration_runner.py` — Plugin DB migration runner + validator (tenant_id check)
- `app/plugins/builtins/__init__.py` — Built-in plugins directory (empty for now, just structure)
- `tests/test_plugins.py` — Plugin lifecycle tests (install/activate/deactivate/uninstall/idempotent)
### Modify:
- `app/models/__init__.py` — Register Plugin, PluginMigration
- `app/routes/__init__.py` — Register plugins router
- `app/schemas/__init__.py` — Register plugin schemas
- `app/services/__init__.py` — Register plugin_service
- `app/main.py` — Initialize plugin registry on startup (discover builtins)
- `app/core/event_bus.py` — Ensure register/unregister listener API works for plugins
- `app/core/service_container.py` — Ensure plugins can receive db, cache, event_bus, storage, notifications
- `tests/conftest.py` — Add plugins, plugin_migrations to TRUNCATE list
- `alembic/versions/` — New migration for plugins + plugin_migrations tables
## Plugin Lifecycle Design
```
discovered → installed → active → inactive → uninstalled
↑ ↓
└──────────────────────┘ (can re-activate)
```
## Plugin Manifest Schema (Pydantic v2)
```python
class PluginManifest(BaseModel):
name: str # unique identifier
version: str # semver
display_name: str
description: str
dependencies: list[str] = [] # other plugin names required
routes: list[dict] = [] # route definitions
events: list[str] = [] # event names to listen
migrations: list[str] = [] # migration file names
permissions: list[str] = [] # required permissions
```
## BasePlugin Abstract Class
```python
class BasePlugin(ABC):
manifest: PluginManifest
async def on_install(self, db, service_container): ...
async def on_activate(self, db, service_container, event_bus): ...
async def on_deactivate(self, db, service_container, event_bus): ...
async def on_uninstall(self, db, service_container): ...
def get_routes(self) -> list[APIRouter]: ...
```
## Forbidden Patterns
- NO JWT tokens (session-based auth from T01)
- NO SQLite (PostgreSQL only)
- NO wildcard CORS
- NO raw SQL without tenant context
- NO hardcoded secrets
- NO .test TLD emails (use .com)
- NO `SET LOCAL` with bound params (use `SELECT set_config()`)
- NO raising HTTPException in middleware (return JSONResponse)
- NO POST without status_code=201 (where applicable)
- NO plugin tables without tenant_id column (validator enforces)
## Test Spec
- Commands: `cd /a0/usr/workdir/dev-projects/leocrm && source venv/bin/activate && python -m pytest tests/test_plugins.py -v --tb=short`
- Coverage: `python -m pytest tests/test_plugins.py --cov=app/plugins --cov-report=term-missing`
- Target: 85% for plugin modules
- All 14 ACs must pass
## Token Rule
- Use `text_editor:read` for MODIFY, `text_editor:write` for NEW, `code_execution_tool:terminal` for test runs
- Reference files by path, not inline
- Multi-file output: separate files, not one big file
## JSON Tool Examples
```json
{"tool_name":"text_editor","tool_args":{"action":"write","path":"/a0/usr/workdir/dev-projects/leocrm/app/plugins/base.py","content":"..."}}
```
```json
{"tool_name":"code_execution_tool","tool_args":{"runtime":"terminal","session":0,"code":"cd /a0/usr/workdir/dev-projects/leocrm && source venv/bin/activate && python -m pytest tests/test_plugins.py -v --tb=short"}}
```