diff --git a/AGENTS.md.bak b/AGENTS.md.bak deleted file mode 100644 index 9ba1d82..0000000 --- a/AGENTS.md.bak +++ /dev/null @@ -1,571 +0,0 @@ -# LeoCRM — AGENTS.md - -**Projekt:** leocrm -**Erstellt:** 2026-06-28 -**Status:** Draft — ready for implementation - ---- - -## 1. Build & Test Commands - -### Backend (Python / FastAPI) - -#### Setup -```bash - -python -m venv .venv -source .venv/bin/activate -pip install -e ".[dev]" -``` - -#### Run Dev Server -```bash - -uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 -``` - -#### Database Migrations (Alembic) -```bash - -# Generate migration after model changes -alembic revision --autogenerate -m "description" -# Apply migrations -alembic upgrade head -# Rollback one migration -alembic downgrade -1 -``` - -#### Run All Backend Tests -```bash - -python -m pytest -v --tb=short -``` - -#### Run Specific Test File -```bash - -python -m pytest tests/test_auth.py -v --tb=short -``` - -#### Run Tests with Coverage -```bash - -python -m pytest --cov=app --cov-report=term-missing --cov-report=html -``` - -#### Run Tests with Grep Filter -```bash - -python -m pytest -k 'tenant or auth' -v -``` - -#### Type Checking -```bash - -mypy app/ --ignore-missing-imports -``` - -#### Linting -```bash - -ruff check app/ -ruff format app/ -``` - -### Frontend (React / Vite / TypeScript) - -#### Setup -```bash -cd frontend -npm install -``` - -#### Run Dev Server -```bash -cd frontend -npm run dev -``` - -#### Build Production -```bash -cd frontend -npm run build -``` - -#### Run All Frontend Tests -```bash -cd frontend -npx vitest run --reporter=verbose -``` - -#### Run Tests with Coverage -```bash -cd frontend -npx vitest run --coverage -``` - -#### Run Tests in Watch Mode (dev) -```bash -cd frontend -npx vitest watch -``` - -#### Type Checking -```bash -cd frontend -npx tsc --noEmit -``` - -#### Linting -```bash -cd frontend -npx eslint src/ --ext .ts,.tsx -``` - -### Docker Compose (Full Stack) - -#### Build All Services -```bash -docker compose build -``` - -#### Start All Services -```bash -docker compose up -d -``` - -#### View Logs -```bash -docker compose logs -f backend -``` - -#### Stop All Services -```bash -docker compose down -``` - -#### Validate Compose Config -```bash -docker compose config --quiet -``` - -### E2E Tests (Playwright) - -```bash -cd e2e -npx playwright install -npx playwright test -``` - ---- - -## 2. Test Rules - -### TDD (Test-Driven Development) - -- **Red-Green-Refactor:** Write failing test first → implement minimum code to pass → refactor. -- **Every new endpoint gets a test BEFORE implementation.** -- **Every bug fix starts with a reproduction test.** - -### Coverage Targets - -| Layer | Coverage Target | Measured By | -|-------|----------------|-------------| -| Backend Core (app/core/) | 85% | pytest-cov | -| Backend Models+Services | 85% | pytest-cov | -| Backend Routes | 85% | pytest-cov | -| Backend Plugins | 80% | pytest-cov | -| Frontend Components | 75% | vitest coverage | -| Frontend Plugin UI | 70% | vitest coverage | -| E2E (critical paths) | 100% of defined specs | Playwright | - -### Test File Structure - -#### Backend -``` -backend/tests/ -├── conftest.py — Fixtures: test client, test DB, auth helpers, seed data -├── test_auth.py — Auth endpoints, RBAC, password reset -├── test_tenant.py — Tenant isolation, cross-tenant access -├── test_companies.py — Company CRUD, search, filter, pagination, soft-delete -├── test_contacts.py — Contact CRUD, N:M links, GDPR delete -├── test_import_export.py — CSV import/export, XLSX export, dry-run preview -├── test_plugins.py — Plugin lifecycle, event bus, migrations -├── test_dms.py — DMS folders, files, upload, shares, permissions -├── test_calendar.py — Entries, recurrence, kanban, ICS, resources -├── test_mail.py — Accounts, IMAP sync, send, threading, rules, PGP -├── test_tags.py — Tag CRUD, assignment, bulk -├── test_notifications.py — Notification CRUD, unread count -├── test_health.py — Health endpoint -├── test_ai_copilot.py — KI-Copilot API, RBAC enforcement, history -├── test_workflows.py — Workflow CRUD, instances, approval/rejection, event triggers -├── test_monitoring.py — Extended health, Prometheus metrics, alerting -└── test_performance.py — 200k seed, list <500ms, FTS <500ms, streaming export -``` - -#### Frontend -``` -frontend/src/__tests__/ -├── components/ — UI component unit tests (Button, Input, Modal, Table, etc.) -├── features/ — Feature integration tests (CompanyList, ContactForm, etc.) -├── hooks/ — Custom hook tests (useDebounce, usePagination, etc.) -├── plugins/ — Plugin UI tests (DMS, Calendar, Mail, Tags) -└── search/ — Global search tests -``` - -#### E2E -``` -e2e/ -├── auth.spec.ts — Login → logout flow -├── company-crud.spec.ts — Create → edit → delete company -├── contact-crud.spec.ts — Create → link to company → delete -├── search.spec.ts — Global search -└── plugin-toggle.spec.ts — Activate/deactivate plugin -``` - -### Test Conventions - -- **Test names:** `test___` (e.g., `test_login_with_invalid_credentials_returns_401`) -- **Test structure:** Arrange → Act → Assert (AAA pattern) -- **Fixtures:** Use `conftest.py` for shared fixtures. No fixture duplication across files. -- **Test DB:** Use in-memory or ephemeral PostgreSQL (via testcontainers or pytest-postgresql). NEVER test against production DB. -- **Mocking:** Mock external services (SMTP, IMAP, OnlyOffice) in tests. Use `unittest.mock.AsyncMock` for async mocks. -- **Assertions:** Use pytest's native `assert` for backend, `expect()` from `@testing-library/jest-dom` for frontend. -- **No flaky tests:** Tests must be deterministic. Use explicit waits, not sleeps. -- **Test isolation:** Each test must be independent. No test depends on another test's side effects. - -### Don't Modify Tests Rule - -- **NEVER modify existing tests to make them pass.** If a test fails, fix the code, not the test. -- **Exception:** If the test itself is wrong (testing incorrect behavior), document why and get approval before changing. -- **Test files are owned by the QA process, not the implementer.** - ---- - -## 3. Conventions - -### Backend Structure - -``` -backend/app/ -├── main.py — FastAPI app entry point, lifespan, middleware registration -├── config.py — Pydantic Settings (reads from env vars) -├── deps.py — FastAPI dependency injection (auth, db, tenant, permissions) -├── core/ — Core infrastructure (cross-cutting concerns) -│ ├── db/ — SQLAlchemy engine, session factory, base model -│ ├── tenant.py — TenantMixin, ORM auto-filter, tenant context -│ ├── auth.py — Session auth, password hashing (bcrypt), RBAC -│ ├── event_bus.py — Async in-process event bus -│ ├── service_container.py — DI container -│ ├── storage.py — File storage (local/S3) -│ ├── cache.py — Redis cache wrapper -│ ├── jobs.py — ARQ job queue integration -│ ├── notifications.py — Notification service -│ └── audit.py — Audit log middleware -├── models/ — SQLAlchemy ORM models (one file per domain) -├── schemas/ — Pydantic schemas (request/response, one file per domain) -├── services/ — Business logic (one file per domain) -├── routes/ — FastAPI routers (one file per domain) -├── plugins/ — Plugin system -│ ├── registry.py — Plugin discovery, registration -│ ├── manifest.py — Plugin manifest Pydantic schema -│ ├── lifecycle.py — Install/activate/deactivate/uninstall -│ ├── migrations.py — Plugin DB migration runner -│ ├── ui_registry.py — Plugin UI component registration -│ └── builtins/ — Built-in plugins -│ ├── dms/ — DMS plugin -│ ├── calendar/ — Calendar plugin -│ ├── mail/ — Mail plugin -│ └── tags/ — Tags plugin -└── utils/ — Shared utilities (validation, export, import) -``` - -### Backend Naming Conventions - -- **Files:** `snake_case.py` (e.g., `company_service.py`) -- **Classes:** `PascalCase` (e.g., `CompanyService`, `CompanyModel`) -- **Functions/Methods:** `snake_case` (e.g., `get_company_by_id`) -- **Constants:** `UPPER_SNAKE_CASE` (e.g., `SESSION_TIMEOUT_HOURS`) -- **Models:** `Model` suffix or just `` (e.g., `Company`, `Contact`) -- **Schemas:** `Create`, `Update`, `Read`, `List` (Pydantic) -- **Services:** `Service` (e.g., `CompanyService`) -- **Routers:** `_router` variable, file name `_router.py` -- **Tests:** `test_.py` (e.g., `test_companies.py`) - -### Backend Code Conventions - -- **Async first:** All route handlers and service methods are `async def`. -- **Type hints:** All function signatures have type hints (Python 3.12+ syntax). -- **Docstrings:** All public functions/classes have docstrings (Google style). -- **Error handling:** Use FastAPI `HTTPException` with proper status codes. Never raise generic `Exception`. -- **Validation:** Pydantic schemas validate input. Never validate in routes directly. -- **Tenant scoping:** Never query without tenant filter (ORM auto-filter handles this, but be aware). -- **UUID:** All IDs are UUID. Never use integer auto-increment. -- **Timestamps:** All datetime fields are `TIMESTAMPTZ`. Never use naive datetime. -- **Soft-delete:** Use `deleted_at IS NULL` filter. Never hard-delete without explicit `gdpr=true` flag. -- **Audit:** All mutations must create audit log entries. Use the audit middleware/decorator. - -### Frontend Structure - -``` -frontend/src/ -├── main.tsx — React entry point -├── App.tsx — Root component, router, providers -├── api/ — API client (axios), interceptors, endpoint definitions -├── components/ — Shared UI components -│ ├── layout/ — Shell, Sidebar, TopBar, ContentArea -│ ├── ui/ — Button, Input, Select, Modal, Toast, Table, Card, Badge, Avatar -│ └── shared/ — EmptyState, LoadingState, ConfirmDialog, Pagination, Skeleton -├── features/ — Feature modules (one folder per feature) -│ ├── auth/ — Login, PasswordReset -│ ├── companies/ — CompanyList, CompanyDetail, CompanyForm -│ ├── contacts/ — ContactList, ContactDetail, ContactForm -│ ├── settings/ — SettingsTree, ProfileSettings, RoleEditor -│ ├── audit/ — AuditLog -│ ├── dashboard/ — Dashboard -│ └── search/ — GlobalSearch -├── plugins/ — Plugin UI loading framework -│ ├── PluginRegistry.tsx — Fetch manifests, register components -│ └── PluginLoader.tsx — Dynamic lazy-loading of plugin components -├── hooks/ — Custom React hooks (useDebounce, usePagination, useAuth, etc.) -├── store/ — Zustand stores (useAuthStore, useUIStore, useTenantStore) -├── i18n/ — react-i18next setup + locale files (de.json, en.json) -├── styles/ — Global CSS, design tokens (Tailwind config), accessibility -└── utils/ — Utilities (format, validation, export, constants) -``` - -### Frontend Naming Conventions - -- **Files:** `PascalCase.tsx` for components (e.g., `CompanyList.tsx`), `camelCase.ts` for utilities (e.g., `apiClient.ts`) -- **Components:** `PascalCase` (e.g., `CompanyList`, `ContactForm`) -- **Hooks:** `use` (e.g., `useDebounce`, `useAuth`) -- **Stores:** `useStore` (e.g., `useAuthStore`, `useUIStore`) -- **Types/Interfaces:** `PascalCase` (e.g., `CompanyData`, `ContactFormValues`) -- **API functions:** `camelCase` (e.g., `getCompanies`, `createContact`) -- **Test files:** `.test.tsx` next to component or in `__tests__/` mirror - -### Frontend Code Conventions - -- **TypeScript strict:** `strict: true` in tsconfig.json. No `any` types. -- **Functional components:** Only function components, no class components. -- **Hooks:** Custom hooks for reusable logic. No inline hooks in JSX. -- **TanStack Query:** Server state via `useQuery` / `useMutation`. No manual fetch in components. -- **Zustand:** Client state only (UI toggles, theme, active tenant). No server data in Zustand. -- **React Hook Form + Zod:** All forms use `react-hook-form` with `zodResolver`. -- **Tailwind CSS:** No custom CSS files (except global + accessibility). Use Tailwind utility classes. -- **i18n:** All user-visible strings go through `t()` from `react-i18next`. No hardcoded strings. -- **Accessibility:** ARIA attributes on all interactive elements. 44px touch targets. Keyboard navigation. -- **Lazy loading:** Plugin components use `React.lazy()` with `Suspense` boundaries. - -### Git Conventions - -- **Branch naming:** `feature/T01-core-infrastructure`, `fix/auth-tenant-isolation`, `hotfix/critical-bug` -- **Commit messages:** Conventional Commits format: - - `feat(core): implement auth system with session-based login` - - `fix(dms): resolve folder permission bypass on move` - - `test(mail): add IMAP sync integration tests` - - `refactor(calendar): extract recurrence engine to separate module` - - `docs(architecture): update ADR-03 with plugin lifecycle details` -- **PR titles:** `[T01] Core Infrastructure + Multi-Tenant + Auth System` -- **Branch from:** `main` (or feature branch for sub-features) -- **Merge strategy:** Squash merge to `main` after review + CI passes - ---- - -## 4. Task-Zuweisung (Subagenten pro Task) - -### Phasen-Plan - -#### v1 Core Phases (Phase 3 — Implementation) - -| Phase | Tasks | Parallel | Subagent Profile | Description | -|-------|-------|----------|-------------------|-------------| -| 1 | T01 | No | implementation_engineer | Foundation: Core, Auth, Multi-Tenant, RLS, Rate Limiting | -| 2 | T02, T03 | Yes (2 agents) | implementation_engineer ×2 | Core entities + Plugin framework parallel | -| 3 | T07a, T09 | Yes (2 agents) | implementation_engineer ×2 | Frontend Shell+Auth+UI Library + KI-Copilot/Workflow parallel | -| 4 | T07b | No | implementation_engineer | Frontend Feature Pages (Companies, Contacts, Settings, Dashboard, Search) | -| 5 | T10 | No | implementation_engineer | Monitoring, Performance, Doku, Environment Config | - -#### v2 Plugin Phases (nach v1 Deployment) - -| Phase | Tasks | Parallel | Subagent Profile | Description | -|-------|-------|----------|-------------------|-------------| -| 6 | T04, T05, T06, T11 | Yes (4 agents) | implementation_engineer ×4 | DMS, Calendar, Mail, Tags+Permissions backends parallel | -| 7 | T08a, T08b, T08c | Yes (3 agents) | implementation_engineer ×3 | Frontend DMS+Tags, Calendar, Mail+Search parallel | - -### Task-to-Subagent Mapping - -| Task ID | Title | Subagent | Dependencies | Phase | Scope | -|---------|-------|----------|--------------|-------|-------| -| T01 | Core Infrastructure + Multi-Tenant + Auth | implementation_engineer | — | 1 | v1 | -| T02 | Company + Contact + Import/Export | implementation_engineer | T01 | 2 | v1 | -| T03 | Plugin System Framework | implementation_engineer | T01 | 2 | v1 | -| T07a | Frontend SPA — Shell, Auth, Routing, i18n, UI Library | implementation_engineer | T01 | 3 | v1 | -| T07b | Frontend SPA — Companies, Contacts, Settings, Dashboard, Search | implementation_engineer | T01, T02, T07a | 4 | v1 | -| T09 | KI-Copilot + Workflow Engine | implementation_engineer | T01, T02 | 3 | v1 | -| T10 | Monitoring + Performance + Doku + Env Config | implementation_engineer | T01, T02 | 5 | v1 | -| T04 | DMS Plugin Backend | implementation_engineer | T01, T03 | 6 | v2 | -| T05 | Calendar Plugin Backend | implementation_engineer | T01, T03 | 6 | v2 | -| T06 | Mail Plugin Backend | implementation_engineer | T01, T03 | 6 | v2 | -| T11 | Tags + Permissions + Entity Links Backend | implementation_engineer | T01, T03 | 6 | v2 | -| T08a | Frontend DMS + Tags + Permissions UI | implementation_engineer | T04, T07b | 7 | v2 | -| T08b | Frontend Calendar UI | implementation_engineer | T05, T07b | 7 | v2 | -| T08c | Frontend Mail + Global Search UI | implementation_engineer | T06, T07b | 7 | v2 | - -### Parallelization Notes - -**v1 Phases:** -- **Phase 2:** T02 (Company/Contact) and T03 (Plugin Framework) are independent after T01 — safe to run in parallel. -- **Phase 3:** T07a (Frontend Shell+Auth+UI Library) depends only on T01. T09 (KI/Workflow) depends on T01+T02. Both can run in parallel if API contracts are frozen. -- **Phase 4:** T07b (Frontend Feature Pages) depends on T07a (UI library, routing, auth) + T02 (company/contact API). Must run after T07a. -- **Phase 5:** T10 (Monitoring+Doku) depends on T01+T02. Can run parallel with T07b. - -**v2 Phases (after v1 deployment):** -- **Phase 6:** T04 (DMS), T05 (Calendar), T06 (Mail), T11 (Tags+Perm) all depend on T01+T03 — safe to run in parallel. -- **Phase 7:** T08a/T08b/T08c depend on T07b + respective backend (T04/T05/T06) — safe to run in parallel. - -### Block Rules - -- Block = max 3 Tasks per implementation block. -- After each block: quality_reviewer review → block_compactor → context_compactor → User checkpoint. -- quality_reviewer and release_auditor do NOT count toward the 3-task limit. -- After 3 blocks (9 tasks): release_auditor runs full audit. -- Token budget: ~3000 tokens per task. If tool result >5000 tokens: context_compactor. - ---- - -## 5. Forbidden Patterns - -### Backend Forbidden - -- ❌ **SQLite:** No SQLite as database. PostgreSQL 16 only (ADR-01). -- ❌ **Jinja2:** No server-side HTML rendering. API-only backend (ADR-03). -- ❌ **Cross-Tenant Data Access:** No query without tenant_id filter. ORM auto-filter must not be bypassed. -- ❌ **Plaintext Passwords:** Passwords must be bcrypt-hashed (cost=12). Never store or log plaintext. -- ❌ **JWT Tokens:** No JWT auth in v1. Session-based auth with HttpOnly cookies only (ADR-05). -- ❌ **Naive Datetime:** All datetime fields must be timezone-aware (TIMESTAMPTZ). Never use `datetime.now()` without tz. -- ❌ **Integer IDs:** All primary keys are UUID. Never use auto-increment integer IDs. -- ❌ **Hard-Delete without GDPR flag:** Companies/Contacts use soft-delete. Hard-delete only with explicit `?gdpr=true`. -- ❌ **Manual Tenant Filter:** Never manually add `.filter(Tenant.id == x)` in services. The ORM auto-filter handles this. -- ❌ **Sync I/O in Routes:** All route handlers are `async def`. Never use blocking I/O (use `asyncpg`, `aiofiles`, etc.). -- ❌ **Raw SQL without Tenant Check:** Any raw SQL query must explicitly include `tenant_id` filter. -- ❌ **Secrets in Code:** No hardcoded secrets. All secrets via environment variables. -- ❌ **Unvalidated Input:** All request bodies validated by Pydantic schemas. Never trust raw request data. -- ❌ **Missing Audit Log:** All create/update/delete operations must create audit log entries. -- ❌ **Plugin Tables without tenant_id:** All plugin-created tables must include `tenant_id` column. The migration validator enforces this. - -### Frontend Forbidden - -- ❌ **Class Components:** No class components. Functional components with hooks only. -- ❌ **Inline Styles:** No `style={{}}` props. Use Tailwind utility classes. -- ❌ **Hardcoded Strings:** No user-visible hardcoded strings. Use `t()` from i18n. -- ❌ **Manual Fetch in Components:** No `fetch()` or `axios` calls in components. Use TanStack Query hooks. -- ❌ **Server Data in Zustand:** Zustand is for client state only. Server data goes in TanStack Query. -- ❌ **`any` Types:** No `any` type. Use proper TypeScript types. -- ❌ **Missing ARIA Attributes:** All interactive elements must have ARIA labels. -- ❌ **Touch Targets < 44px:** All buttons/links must have minimum 44px touch target. -- ❌ **Direct DOM Manipulation:** No `document.getElementById()` or `querySelector()` in components. Use React refs. -- ❌ **Unsafe HTML Rendering:** No `dangerouslySetInnerHTML` without sanitization. Mail bodies must be sanitized (DOMPurify equivalent). - -### Deployment Forbidden - -- ❌ **Running as Root in Container:** Containers run as non-root user (app:app). -- ❌ **Exposed DB Port in Production:** PostgreSQL port (5432) must not be exposed externally in production. -- ❌ **No Health Check:** All services must have Docker health checks configured. -- ❌ **No Volume for Storage:** File storage must use a named volume, not ephemeral container storage. -- ❌ **Secrets in docker-compose.yml:** No secrets in compose file. Use `.env` file or Docker secrets. - ---- - -## 6. Quality Gates - -### Per-Task Quality Gate - -Before a task is marked complete: -1. All test_spec commands must pass. -2. Coverage target must be met (measured by pytest-cov / vitest coverage). -3. TypeScript compiles without errors (`tsc --noEmit`). -4. Linting passes (ruff for backend, eslint for frontend). -5. Build succeeds (Vite build for frontend, no build step for backend). -6. No forbidden patterns detected. -7. All acceptance criteria verified as testable. - -### Phase Gate (after each phase) - -1. All tasks in the phase pass their quality gates. -2. quality_reviewer subagent reviews the phase output. -3. No critical issues from quality_reviewer. -4. Block compactor saves progress. -5. User checkpoint before next phase. - -### Release Gate (before v1 deployment) - -1. All 7 v1 tasks complete (T01, T02, T03, T07a, T07b, T09, T10). -2. release_auditor runs full audit. -3. Docker Compose builds and starts successfully. -4. Health endpoint returns 200. -5. E2E tests (Playwright) pass. -6. All forbidden patterns checked. - -### v2 Release Gate (before v2 plugin deployment) - -1. All 7 v2 tasks complete (T04, T05, T06, T11, T08a, T08b, T08c). -2. release_auditor runs full audit. -3. All plugin backends + frontends pass quality gates. -4. Plugin install/activate/deactivate lifecycle tested. -5. All forbidden patterns checked. - ---- - -## 7. Environment Setup - -### Development Environment - -| Variable | Value | Purpose | -|----------|-------|---------| -| `POSTGRES_HOST` | `localhost` (dev) / `postgres` (docker) | Database host | -| `POSTGRES_PORT` | `5432` | Database port | -| `POSTGRES_DB` | `leocrm` | Database name | -| `POSTGRES_USER` | `leocrm` | Database user | -| `POSTGRES_PASSWORD` | (from .env) | Database password | -| `REDIS_URL` | `redis://localhost:6379/0` | Redis for cache + sessions + jobs | -| `LEOCRM_SECRET_KEY` | (min 32 chars) | Session signing secret | -| `SESSION_TIMEOUT_HOURS` | `8` | Session expiry | -| `MAIL_ENCRYPTION_KEY` | (32-byte hex) | AES-256 key for mail credentials | -| `STORAGE_BACKEND` | `local` (dev) / `s3` (prod) | File storage backend | -| `STORAGE_PATH` | `/data/leocrm/storage` | Local storage path | -| `ONLYOFFICE_URL` | `http://onlyoffice:80` | OnlyOffice document server | -| `LOG_LEVEL` | `INFO` | Logging level | - -### Test Environment - -- Test DB: Ephemeral PostgreSQL (pytest-postgresql or testcontainers). -- Test Redis: Ephemeral or fakeredis. -- External services (IMAP, SMTP, OnlyOffice): Mocked via `unittest.mock.AsyncMock`. -- Test fixtures in `conftest.py` provide: test client, authenticated client (per role), seeded data. - ---- - -## 8. Architecture Reference - -Full architecture details: `architecture.md` - -Full task graph with test specs: `task_graph.json` - -Key ADRs: -- ADR-01: PostgreSQL 16 (not SQLite) -- ADR-02: ARQ (not Celery) -- ADR-03: Built-in plugins with manifest (not dynamic pip-install) -- ADR-04: TanStack Query (not Redux) -- ADR-05: Session-based auth (not JWT) -- ADR-06: Soft-delete with `deleted_at` column - ---- - -## Handoff - -- **AGENTS.md status:** COMPLETE -- **task_graph.json status:** COMPLETE (14 tasks: 7 v1 + 7 v2, all with test_spec, 143 features covered, v1/v2 separated, v2.1.0) -- **architecture.md status:** COMPLETE (73/73 v1 features referenced, v2 sections marked) -- **Ready for v1 implementation:** YES (pending quality_reviewer review + plan_mode transition to implementation_allowed) -- **v2 implementation:** After v1 deployment, separate phase diff --git a/ENTERPRISE_RBAC_PLAN.md b/ENTERPRISE_RBAC_PLAN.md deleted file mode 100644 index 628d63c..0000000 --- a/ENTERPRISE_RBAC_PLAN.md +++ /dev/null @@ -1,210 +0,0 @@ -# Enterprise RBAC Plan — LeoCRM - -## Gesamt: 23 Sprints, 74 Features, 230h - -### Sprint 1 — Fundament (14h) -- [ ] entity_permissions Tabelle + expires_at + Migration 0049 -- [ ] OwnedMixin + owner_id auf allen Models + Migration 0050 -- [ ] Universeller Permission Service (CRUD + get_effective_access + get_visible_ids) -- [ ] Universelle Permission API (5 Endpoints) -- [ ] Redis-Cache für Entity-Permissions (Bitmap) -- [ ] PostgreSQL RLS Policies + set_user_context() -- [ ] Rate Limiting auf Permission-Änderungen -- [ ] Folder ACLs in entity_permissions migrieren (Migration 0051) - -### Sprint 2 — Row-Level Security (16h) -- [ ] apply_visibility_filter() Helper -- [ ] Query-Filter in alle 28 Routes -- [ ] Child-Entity-Vererbung -- [ ] Batch-Resolution -- [ ] BaseSearchProvider mit Visibility-Filter -- [ ] ContactDetail/ContactsList Permission-Checks -- [ ] Copy/Duplicate Permission -- [ ] EXISTS-Optimization für RLS - -### Sprint 3 — Search/Dashboard/Export (13h) -- [ ] GlobalSearch Visibility-Filter -- [ ] Two-Phase Search -- [ ] Search-Index Pre-Filter -- [ ] Dashboard-Counts pro User -- [ ] Export-Filter -- [ ] Reports-Filter -- [ ] Frontend-Filter für alle 4 - -### Sprint 4 — Field-Level komplett (10h) -- [ ] Custom Field Sensitivity -- [ ] Field Definitions für alle Entities + Plugin-Registration -- [ ] filter_fields_by_permission() in alle Responses -- [ ] Field-Level Permission Editor UI -- [ ] Frontend: readonly/hidden in ContactDetail + ContactsList + DMS + Mail + AI - -### Sprint 5 — Sharing UI (8h) -- [ ] Universeller ShareDialog Komponente -- [ ] Share-Button in 8 Detail-Ansichten -- [ ] Owner-Spalte in 8 Listen -- [ ] Permission-UI (Buttons ausblenden) -- [ ] Permission-Expiration UI - -### Sprint 6 — Notifications + Audit + Real-time (10h) -- [ ] Permission-Change-Notifications -- [ ] Audit-Trail für Permission-Änderungen -- [ ] Notification-Entity-Filter -- [ ] Real-time WebSocket Sync -- [ ] Redis Pub/Sub für WebSocket Fan-Out - -### Sprint 7 — E-Mail Postfächer (8h) -- [ ] Mailbox owner_id + Migration -- [ ] Mailbox Permissions (entity_permissions) -- [ ] Mail Permission Migration -- [ ] Mail-Query-Filter -- [ ] Mail-Field-Level -- [ ] Frontend: Mailbox-Liste + Mail-Liste + Mail-Detail - -### Sprint 8 — Plugin Entities (14h) -- [ ] DMS owner_id + Permissions + Migration -- [ ] Calendar owner_id + Permissions + Migration -- [ ] Tasks owner_id + Permissions + Migration -- [ ] Kommunikation RBAC Migration -- [ ] Entity Links Permission -- [ ] Tags Permission -- [ ] 15 Plugin Entity Registration -- [ ] DMS Permission Migration -- [ ] Folder-Path-Materialization -- [ ] Frontend Permission-Checks für DMS + Calendar + Tasks - -### Sprint 9 — App-Sichtbarkeit (7h) -- [ ] Plugin Manifest permission Feld -- [ ] tenant_plugin_activation Tabelle + API -- [ ] Sidebar Permission-Filter -- [ ] TopBar Permission-Filter -- [ ] Settings-Navigation Permission-Filter -- [ ] Route-Guards (ProtectedRoute) - -### Sprint 10 — Advanced Security + AI + WebSocket (18h) -- [ ] API-Token Scopes -- [ ] Webhook Scope Filter -- [ ] Workflow Scope Filter -- [ ] Contact Merge Permission-Check -- [ ] AI Copilot Permission-Aware (process_query + execute_action) -- [ ] AI Tool Registry -- [ ] AI System Prompt mit Permission-Context -- [ ] AI Proactive Permission-Aware -- [ ] AI UI Control Permission-Checks -- [ ] MCP Permission-Scopes -- [ ] Automation Permission-Checks -- [ ] WebSocket Permission-Checks -- [ ] Event Bus Permission-Filter -- [ ] Frontend: AI + Notifications + Workflows + DedupMerge - -### Sprint 11 — Owner Management (5h) -- [ ] Owner-Transfer (Bulk) API -- [ ] Auto-Transfer bei User-Deaktivierung -- [ ] Backup/Restore Permissions -- [ ] Frontend Owner-Transfer-UI - -### Sprint 12 — Zentrale Einstellungsseite (9h) -- [ ] Rechte-Settings-Page mit Tabs -- [ ] Freigaben-Übersicht (Admin-Dashboard) -- [ ] Audit-View für Permission-Changes -- [ ] CustomFields Sensitivity UI -- [ ] App-Sichtbarkeit-Tab - -### Sprint 13 — ABAC Engine (18h) -- [ ] entity_policies Tabelle + Migration -- [ ] Policy-Engine: JSONB → SQLAlchemy Übersetzer -- [ ] apply_policy_filter() + Integration mit RBAC-Filter -- [ ] Policy-Cache (Redis) + Invalidation -- [ ] Policy Service (CRUD) -- [ ] Policy API (5 Endpoints) -- [ ] GIN-Indexes für ABAC -- [ ] Pre-compiled SQL Fragments -- [ ] Policy-Intersection-Optimization -- [ ] Materialized Policy Result - -### Sprint 14 — ABAC UI (10h) -- [ ] ABAC Rule-Editor mit AND/OR Gruppen -- [ ] Feld-Auswahl (Core + Custom Fields) -- [ ] Vorschau + Test-Tool -- [ ] Custom Field ABAC Support (JSONB-Path) - -### Sprint 15 — Templates & Automation (5h) -- [ ] permission_templates Tabelle + Migration -- [ ] Default-Policies für neue Entities -- [ ] Auto-Share bei Erstellung -- [ ] Frontend Template-Editor UI - -### Sprint 16 — Mass & Bulk (4h) -- [ ] Bulk-Share API -- [ ] Mass-Operations -- [ ] Frontend Bulk-Share-UI - -### Sprint 17 — Analytics & Konflikte (5h) -- [ ] Permission-Analytics API -- [ ] Konflikt-Erkennung -- [ ] Orphaned-Permissions-Cleanup -- [ ] Frontend Analytics-Dashboard - -### Sprint 18 — Delegation (4h) -- [ ] permission_delegations Tabelle + Migration -- [ ] Delegation Service + API -- [ ] Abwesenheits-UI -- [ ] Auto-Expiry - -### Sprint 19 — Resolution-Strategien (3h) -- [ ] Konfigurierbare Override-Regeln -- [ ] Tenant-Einstellung -- [ ] Frontend UI - -### Sprint 20 — Tests (12h) -- [ ] Backend: Entity Permissions Tests -- [ ] Backend: ABAC Tests -- [ ] Backend: Performance Tests (100K Datensätze) -- [ ] Backend: Search Permission Tests -- [ ] Backend: WebSocket Permission Tests -- [ ] Frontend: ProtectedRoute Tests -- [ ] Frontend: Permission-UI Tests -- [ ] Frontend: ShareDialog Tests - -### Sprint 21 — Dokumentation (3h) -- [ ] docs/permissions.md -- [ ] docs/permissions_plugin_dev.md -- [ ] Plugin Template mit Permission-Beispielen -- [ ] API-Docs - -### Sprint 22 — Guest Access (28h) -- [ ] guest_users Tabelle + Migration -- [ ] Guest Auth (Login, Session, Logout) -- [ ] Guest Permission Resolution (Service + RLS) -- [ ] Guest Invitation Flow (Backend + E-Mail) -- [ ] Guest API (limited endpoints) -- [ ] Guest Frontend (vereinfachtes Layout + Views) -- [ ] Guest Permission Management UI (Settings) -- [ ] Guest Expiration & Auto-Cleanup -- [ ] Guest Audit Trail -- [ ] Guest Security (IP-Whitelist, Rate Limit, Watermarking) -- [ ] Guest Tests - -### Sprint 23 — Infrastructure (4h) -- [ ] PgBouncer Setup -- [ ] Audit Log Partitioning -- [ ] Connection Pool Config - -## Permission Levels -| Level | Sichtbar? | Bearbeiten? | Löschen? | Teilen? | -|-------|:---:|:---:|:---:|:---:| -| Owner | ✅ | ✅ | ✅ | ✅ | -| Admin | ✅ | ✅ | ✅ | ✅ | -| Write | ✅ | ✅ | ❌ | ❌ | -| Read | ✅ | ❌ | ❌ | ❌ | -| None | ❌ | ❌ | ❌ | ❌ | - -## Architecture -- PostgreSQL RLS (Safety Net) -- Materialized View (user_entity_visibility) -- Redis Bitmap Cache -- Batch-Resolution -- GIN-Indexes (ABAC + JSONB) -- Folder-Path-Materialization (GiST) -- PgBouncer Connection Pool -- Redis Pub/Sub WebSocket Fan-Out -- Audit Log Partitioning diff --git a/FIX-PLAN-V2.md b/FIX-PLAN-V2.md deleted file mode 100644 index 13e822a..0000000 --- a/FIX-PLAN-V2.md +++ /dev/null @@ -1,323 +0,0 @@ -# LeoCRM Fix-Plan V2 — Gründliche Analyse & Maßnahmen - -*Erstellt: 2026-07-26 — basierend auf externem Audit + eigener Code-Verifikation* - ---- - -## Zusammenfassung - -Von 16 zentralen Punkten des externen Audits wurden **alle 16 durch Code-Inspektion verifiziert**. Zusätzlich wurden **5 neue Probleme** gefunden (UploadFile-Bug, Redis-Default-Passwort, exponierte Ports, unauthentifizierter Error-Endpoint, fehlende Security-Headers). - -**Gesamtstatus:** Alle Phasen implementiert (Stand 2026-07-27). M5 (Frontend-Integration) als letzte Phase abgeschlossen. - ---- - -## Implementierungs-Status (Stand 2026-07-27) - -Die folgenden Phasen wurden gemäß Git-Historie implementiert: - -| Phase | Commit | Maßnahmen | Status | -|-------|--------|-----------|--------| -| **Phase 1** (B1-B10) | `5ec1fc9` | Kritische Release-Blocker: Redis-Singleton (B1), Plugin-Routen (B2), UploadFile response_model (B3), DMS-Streaming (B4), Outbox-Worker (B5), Passwort-Reset-Mail (B6), Webhook-SSRF (B7), RLS-DB-Role (B8), .env-Korrektur (B9), Redis-Ports (B10) | ✅ Implementiert | -| **Phase 2** (H1-H7) | `604a2b7` | Error-Endpoint (H1), Rate-Limiter (H2), CSRF-Redis (H3), WebSocket-Auth (H4), File-Upload (H5), Security-Headers (H6), Migration-Repair (H7) | ✅ Implementiert | -| **Phase 3** (M1-M4, M6) | `825d638` | Passwort-Komplexität (M1), Login-Response (M2), Permission-Cache (M3), ENVIRONMENT (M4), weitere (M6) | ✅ Implementiert | -| **Phase 4** | `b6e3afd` | Webhooks, Backup/Restore UI, Onboarding/Tutorial | ✅ Implementiert | -| **Plugin-System-Umbau** | `98eb1d0` | Plugin-Routen nur in create_app(), require_active_plugin() Dependency, WebSocket-Skip | ✅ Implementiert | - -### Verifizierte P0-Behebungen - -| P0 | Problem | Status | Beweis | -|----|---------|--------|--------| -| P0-1 | Auth-Bypass via X-Internal-Call | ✅ Behoben | `app/deps.py` hat keinen X-Internal-Call Code mehr. Auth nur via Session-Cookie. | -| P0-2 | Destruktive Migrationen | ✅ Behoben | Migration 0021 benennt Tabellen um (`*_old`). Migration 0044 repariert RLS. | -| P0-3 | Plugin-Upload RCE | ✅ Neutralisiert | Alle Upload-Endpoints deaktiviert (403). `_extract_plugin_from_zip()` ist Dead Code. | -| P0-4 | RLS nicht erzwungen | ✅ Behoben | Migration 0028 setzt FORCE RLS. Migration 0044 erstellt `crm_runtime` (NOSUPERUSER, NOBYPASSRLS). | -| P0-5 | Plugin-Doppelregistrierung | ✅ Behoben | Routen nur in create_app(). require_active_plugin() prüft Aktivierungsstatus. | -| P0-6 | Kein persistentes Volume | ✅ Behoben | docker-compose.yml hat volumes für PostgreSQL, Redis, App-Uploads, Worker. | -| P0-7 | Öffentliche Domain | ✅ Behoben | Keine crm.media-on.de Referenz mehr in docker-compose.yml. | - -### Weitere verifizierte Behebungen -- **B1** (doppelte get_redis()): ✅ Nur eine Definition in `app/core/auth.py` Zeile 53 -- **B3** (UploadFile response_model): ✅ `response_model=None` in dms, calendar, mail routes -- **B7** (Webhook SSRF): ✅ Private IP-Check, `follow_redirects=False`, Protokoll-Check -- **B9** (AUTH_SECRET vs SECRET_KEY): ✅ `.env.docker.example` verwendet `SECRET_KEY` -- **B10** (Redis-Default-Passwort + Ports): ✅ Ports auskommentiert, Redis-Passwort required -- **WebSocket Auth**: ✅ Beide WS-Endpunkte haben `verify_ws_origin()`, Session-Cookie-Validierung, `user_id` aus Session - ---- - -## Phase 1: Kritische Release-Blocker (vor Produktivbetrieb) - -### B1. Doppelte `get_redis()` entfernen -- **Datei:** `app/core/auth.py` Zeilen 53 + 94 -- **Problem:** Zweite Definition überschreibt Singleton, erzeugt pro Aufruf neue Verbindung → Connection Leak -- **Fix:** Zweite `def get_redis()` (Zeile 94) löschen. Erste Definition (Zeile 53) beibehalten. -- **Aufwand:** 5 Min -- **Risiko:** Keines — erste Definition ist korrekt - -### B2. Plugin-Routen-Registrierung reparieren -- **Datei:** `app/main.py` Zeilen 375-416 -- **Problem:** Alle Plugin-Routen werden statisch in `create_app()` registriert, unabhängig vom Aktivierungsstatus. Deaktivierte Plugins bleiben erreichbar. Kommentar in Zeile 416 sagt das Gegenteil. -- **Fix:** - 1. Statische Registrierung aus `create_app()` entfernen - 2. In `lifespan()` nur Routen für `active=True` Plugins registrieren - 3. `Depends(require_active_plugin("name"))` als zentrale Prüfung ergänzen - 4. Bei Deaktivierung: Router entfernen oder 403-Dependency ergänzen -- **Aufwand:** 2-3 Std -- **Risiko:** Mittel — muss sicherstellen dass keine Route doppelt registriert wird - -### B3. UploadFile Route-Registration Bug -- **Dateien:** `app/plugins/builtins/dms/routes.py`, `calendar/routes.py`, `mail/routes.py`, `kommunikation/routes.py`, `ai_assistant/routes.py` -- **Problem:** FastAPI kann `UploadFile` nicht als Response-Model auflösen → 5 Plugins failen beim Registrieren mit `Invalid args for response field` -- **Fix:** `response_model=None` zu allen Endpoints mit `UploadFile`-Rückgabe hinzufügen, oder Return-Type auf `Response`/`dict` ändern -- **Aufwand:** 30 Min -- **Risiko:** Keines — Routen sind aktuell gar nicht registriert - -### B4. DMS-Upload auf echtes Streaming umstellen -- **Datei:** `app/plugins/builtins/dms/routes.py` Zeilen 444-472 -- **Problem:** Chunks werden in `list[bytes]` gesammelt, dann `b"".join()` → 100MB Datei = 200MB+ RAM. `save_stream()` existiert aber wird nicht benutzt. -- **Fix:** - ```python - async def chunk_generator(): - while chunk := await file.read(CHUNK_SIZE): - yield chunk - await storage.save_stream(storage_path, chunk_generator()) - ``` - Hash und Größe während des Streams berechnen. -- **Aufwand:** 1 Std -- **Risiko:** Gering — save_stream() ist bereits implementiert - -### B5. Outbox-Worker: Event-Handler registrieren -- **Datei:** `app/core/worker.py` `on_startup()` -- **Problem:** Worker liest Events aus Outbox, published an lokalen EventBus, aber es sind keine Handler registriert → Events werden als `published` markiert ohne Verarbeitung -- **Fix:** - 1. In `on_startup()`: Plugin-Event-Handler registrieren (wie in `lifespan()` der API) - 2. `webhook_dispatcher._dispatch_event` an EventBus subscriben - 3. Plugin-Participant-Handler registrieren -- **Aufwand:** 2 Std -- **Risiko:** Mittel — muss gleiche Handler wie API-Container registrieren - -### B6. Passwort-Reset-Mailjob implementieren -- **Dateien:** `app/services/auth_service.py`, `app/core/jobs.py`, `app/core/job_registry.py` -- **Problem:** `send_password_reset_email` Job wird gequeued aber nie registriert → Mail wird nicht versendet. Token wird in Logs geschrieben (Zeile 240-241). -- **Fix:** - 1. `send_password_reset_email` Worker-Funktion implementieren (SMTP/IMAP) - 2. Mit `register_job()` registrieren - 3. `logger.warning("raw_token for development: %s", raw_token)` entfernen - 4. Token nur im Development-Mode loggen, nie in Production -- **Aufwand:** 2 Std -- **Risiko:** Gering - -### B7. Webhook SSRF-Schutz + Secret-Behandlung -- **Dateien:** `app/services/webhook_service.py`, `app/schemas/webhook.py` -- **Problem:** Kein SSRF-Schutz — User können interne Dienste ansprechen (redis:6379, postgres:5432, 169.254.169.254). Webhook-Secret wird im Response zurückgegeben. -- **Fix:** - 1. SSRF-Prüfung: DNS auflösen, private IPs blocken (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x, ::1) - 2. Redirects deaktivieren oder prüfen - 3. Protokoll-Allowlist (nur https) - 4. `secret` aus `WebhookResponse` entfernen - 5. Secret gehasht in DB speichern -- **Aufwand:** 3 Std -- **Risiko:** Gering - -### B8. RLS: Separater DB-Runtime-User -- **Dateien:** `docker-compose.yml`, `alembic/versions/0044_db_roles.py` (neu) -- **Problem:** `POSTGRES_USER` (crm_user) ist Superuser → umgeht RLS auch mit FORCE. Spätere Tabellen (user_preferences, saved_filters, etc.) haben keine RLS-Policy. -- **Fix:** - 1. Neue Migration `0044_db_roles.py`: erstellt `crm_runtime` (NOSUPERUSER, NOBYPASSRLS) - 2. `crm_runtime` bekommt nur SELECT/INSERT/UPDATE/DELETE Rechte - 3. `docker-compose.yml`: API und Worker nutzen `crm_runtime`, Migrationen nutzen `crm_owner` - 4. Neue Migration `0045_rls_new_tables.py`: RLS für alle Tabellen mit `tenant_id` die nach 0028 hinzukamen -- **Aufwand:** 4 Std -- **Risiko:** Hoch — muss bestehende Datenbanken migrieren ohne Datenverlust - -### B9. .env.docker.example korrigieren -- **Datei:** `.env.docker.example` -- **Problem:** Verwendet `AUTH_SECRET` statt `SECRET_KEY` (config.py erwartet `SECRET_KEY`) -- **Fix:** `AUTH_SECRET` → `SECRET_KEY` umbenennen -- **Aufwand:** 5 Min -- **Risiko:** Keines - -### B10. Redis-Default-Passwort + exponierte Ports -- **Datei:** `docker-compose.yml` -- **Problem:** Redis-Passwort default `changeme`, PostgreSQL (5432) und Redis (6379) Ports exponiert -- **Fix:** - 1. Redis-Passwort als Required-Env ohne Default - 2. `ports:` Sektion für DB und Redis entfernen (nur internes Docker-Netzwerk) - 3. Falls Debug-Zugriff nötig: nur an 127.0.0.1 binden -- **Aufwand:** 15 Min -- **Risiko:** Gering — bestehende Setups müssen .env anpassen - ---- - -## Phase 2: Hohe Priorität (kurz nach Release) - -### H1. Unauthentifizierter Error-Endpoint absichern -- **Datei:** `app/routes/errors.py` -- **Problem:** `POST /api/v1/errors` ohne Auth, sendet Daten an Forgejo als öffentliches Issue. Context-Dict kann sensible Daten enthalten. -- **Fix:** - 1. Context-Felder filtern (keine Tokens, Passwörter, Headers) - 2. Forgejo-Issues nur in non-production erstellen - 3. Rate-Limit auf IP-Basis (bereits vorhanden, aber in-memory → bei Multi-Worker unzuverlässig) - 4. Optional: Auth erforderlich, aber dann funktioniert Frontend-Error-Logging nicht mehr → besser: nur sanitisierte Daten akzeptieren -- **Aufwand:** 1 Std - -### H2. Rate-Limiter IP-Spoofing -- **Datei:** `app/core/rate_limit.py` Zeile 43 -- **Problem:** Vertraut `X-Forwarded-For` blind → IP-Spoofing umgeht Rate-Limits -- **Fix:** Nur erste IP in X-Forwarded-For verwenden, oder `X-Real-IP` mit Proxy-Validation -- **Aufwand:** 30 Min - -### H3. CSRF-Middleware Redis-Verbindung -- **Datei:** `app/core/middleware.py` Zeile 69 -- **Problem:** Erstellt pro unsafe Request neue Redis-Verbindung → Connection Leak -- **Fix:** `get_redis()` Singleton verwenden (funktioniert nach B1) -- **Aufwand:** 10 Min - -### H4. WebSocket Auth + Origin-Verifikation -- **Dateien:** `app/plugins/builtins/kommunikation/websocket_manager.py`, `ai_ui_control/websocket_manager.py` -- **Problem:** `user_id` wird ohne Auth-Verifikation akzeptiert. Keine Origin-Prüfung bei WS-Upgrade. -- **Fix:** - 1. Session-Token aus Query-Param oder Header validieren - 2. Origin-Header gegen erlaubte Domains prüfen - 3. User-ID aus Session ableiten, nicht aus Client-Param -- **Aufwand:** 2 Std - -### H5. File-Upload-Sicherheit -- **Datei:** `app/core/storage.py` -- **Problem:** Keine Path-Traversal-Prüfung, keine Type/Size-Limits, `get_url()` leakt Filesystem-Pfade -- **Fix:** - 1. Filename sanitizen (keine `../`, keine absoluten Pfade) - 2. MIME-Type-Allowlist - 3. Max-File-Size konfigurierbar - 4. `get_url()` gibt relative URL zurück, nicht Filesystem-Pfad -- **Aufwand:** 1 Std - -### H6. Security-Headers -- **Datei:** `app/core/middleware.py` (neu) -- **Problem:** Keine Security-Headers (HSTS, X-Content-Type-Options, X-Frame-Options, CSP) -- **Fix:** Middleware ergänzen die diese Headers setzt -- **Aufwand:** 30 Min - -### H7. Migration-Repair für bestehende Installationen -- **Datei:** `alembic/versions/0044_repair_contact_migration.py` (neu) -- **Problem:** Migrationen 0021 und 0027 wurden nachträglich geändert. Alembic führt sie nicht erneut aus. -- **Fix:** - 1. Neue Migration die `*_old` Tabellen erkennt und Daten nachmigriert - 2. Integritätsprüfung (Anzahl vergleichen) - 3. Bei Abweichungen hart abbrechen mit Fehlermeldung -- **Aufwand:** 3 Std - ---- - -## Phase 3: Mittlere Priorität - -### M1. Passwort-Komplexität -- **Datei:** `app/schemas/auth.py`, `app/schemas/user.py` -- **Problem:** Min-Length 8 bei Erstellung, Min-Length 1 bei Login. Keine Komplexitäts-Requirements. -- **Fix:** Passwort-Validator ergänzen (min 8 Zeichen, 1 Groß, 1 Klein, 1 Zahl) -- **Aufwand:** 30 Min - -### M2. Login-Response: is_system_admin -- **Datei:** `app/routes/auth.py` Zeile 78 -- **Problem:** `is_system_admin` Flag in Login-Response leakt interne Rolle -- **Fix:** Flag aus Response entfernen oder nur für Admin-User anzeigen -- **Aufwand:** 15 Min - -### M3. Permission-Cache: Stale Data bei DB-Error -- **Datei:** `app/core/permissions.py` Zeile 337 -- **Problem:** Bei DB-Error fällt Cache auf stale Daten zurück → widerrufene Rechte bleiben aktiv -- **Fix:** Bei DB-Error: Cache invalidieren und 503 zurückgeben statt stale Daten zu nutzen -- **Aufwand:** 30 Min - -### M4. ENVIRONMENT=development vs SESSION_COOKIE_SECURE=true -- **Datei:** `.env` Zeilen 3-4 -- **Problem:** Inkonsistent — development deaktiviert Prod-Safety-Checks, aber Cookie ist secure -- **Fix:** In .env.docker.example klar dokumentieren: production → `ENVIRONMENT=production` + `SESSION_COOKIE_SECURE=true` -- **Aufwand:** 10 Min - -### M5. Frontend: Unresolved Items — ✅ Implementiert (2026-07-27) -- **Dateien:** `WelcomeDialog.tsx`, `SavedFilterBar.tsx`, `EntityHistoryPanel.tsx`, `TagBadge.tsx`, `TagSelector.tsx` -- **Status:** ✅ Implementiert — SavedFilterBar und TagSelector in ContactsList, Mail, Calendar integriert -- **Implementiert:** - 1. SavedFilterBar in ContactsList (entityType="contacts"), Mail (entityType="mail"), Calendar (entityType="calendar") integriert - 2. TagSelector in ContactsList (entityType="contact"), Mail (entityType="file"), Calendar (entityType="calendar_entry") integriert - 3. Frontend TypeScript: 0 Errors (`npx tsc --noEmit`) -- **Hinweis:** WelcomeDialog und EntityHistoryPanel bleiben für spätere Iteration offen - -### M6. Frontend-Tests: QueryClientProvider -- **Datei:** `frontend/src/test/setup.ts` oder einzelne Tests -- **Problem:** ~29 Tests failen mit missing QueryClientProvider -- **Fix:** Globalen Test-Wrapper mit QueryClientProvider in setup.ts ergänzen -- **Aufwand:** 1 Std - ---- - -## Phase 4: Niedrige Priorität - -### L1. document.write() in print.ts -- **Datei:** `frontend/src/utils/print.ts` Zeilen 54, 127 -- **Problem:** `document.write()` mit DOM-Clone — XSS-Risiko wenn Content nicht sanitized -- **Fix:** Statt `document.write()`: `iframe.srcdoc` oder `Blob URL` verwenden -- **Aufwand:** 1 Std - -### L2. AI UI Control: Unbounded Feedback-Storage -- **Datei:** `app/plugins/builtins/ai_ui_control/websocket_manager.py` Zeile 94 -- **Problem:** Feedback/Commands unbegrenzt im Memory gespeichert → Memory Exhaustion -- **Fix:** Max-Length Queue (z.B. 100 Einträge) mit FIFO -- **Aufwand:** 15 Min - -### L3. Backup-Strategie dokumentieren -- **Problem:** Named Volumes in docker-compose aber keine Backup/Restore-Doku -- **Fix:** Backup-Script und Doku ergänzen -- **Aufwand:** 2 Std - ---- - -## Implementierungs-Reihenfolge - -``` -Phase 1 (Release-Blocker): - B1 → B3 → B9 → B10 → B2 → B4 → B5 → B6 → B7 → B8 - ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ - 5m 30m 5m 15m 3h 1h 2h 2h 3h 4h - Gesamt: ~16 Std - -Phase 2 (Hohe Priorität): - H3 → H2 → H6 → H1 → H5 → H4 → H7 - Gesamt: ~8 Std - -Phase 3 (Mittlere Priorität): - M4 → M1 → M2 → M3 → M6 → M5 - Gesamt: ~6 Std - -Phase 4 (Niedrige Priorität): - L2 → L1 → L3 - Gesamt: ~3 Std -``` - -**Gesamtaufwand: ~33 Std** - ---- - -## Was bereits sauber funktioniert - -- ✅ Auth-Bypass entfernt (keine X-Internal-Call/X-Tenant-Id/X-User-Id Headers mehr) -- ✅ Plugin-Upload/URL-Installation deaktiviert (403) -- ✅ Worker in separatem Container -- ✅ Metrics adminbeschränkt -- ✅ DOMPurify für HTML-Komponenten -- ✅ ARQ-Verbindungspool zentralisiert -- ✅ Session-Widerruf nach Passwortänderung -- ✅ Permission-Cache-Versionierung -- ✅ Redis SCAN statt KEYS -- ✅ Rabatte von Float auf Numeric -- ✅ Event-Outbox als Grundlage vorhanden -- ✅ RLS FORCE + WITH CHECK in Migration 0028 -- ✅ Migration 0021: Tabellen umbenennen statt löschen -- ✅ Frontend: TypeScript typecheck clean (0 errors) -- ✅ Frontend: ErrorBoundary, OfflineBanner, ErrorLogger implementiert -- ✅ Frontend: Print/PDF mit WeasyPrint funktioniert -- ✅ Dockerfile: Multi-stage, non-root User, Healthcheck -- ✅ Bcrypt Password-Hashing -- ✅ Session-Tokens: secrets.token_urlsafe(32) diff --git a/FIX-PLAN.md b/FIX-PLAN.md deleted file mode 100644 index f4b81f9..0000000 --- a/FIX-PLAN.md +++ /dev/null @@ -1,88 +0,0 @@ -# LeoCRM — Umfassender Fix-Plan - -> Erstellt: 2026-07-25 -> Letzte Überprüfung: 2026-07-26 — Alle Items gegen Codebasis verifiziert -> Quellen: Externes Audit (geprüft), eigene Code-Inspektion, Coolify-Deployment-Prüfung - ---- - -## ✅ Erledigte Fixes (22 von 24 Items komplett) - -Die folgenden Items wurden bei der Überprüfung am 2026-07-26 als erledigt bestätigt: - -| Item | Beschreibung | Verifiziert durch | -|---|---|---| -| P0-1 | Auth-Bypass entfernt | `app/deps.py` — keine `X-Internal-Call` Headers mehr | -| P0-2 | Migrationen repariert | `migration_0021.sql` gelöscht; Migration 0021 renamed `_old` Tabellen statt DROP; Migration 0027 kopiert `company_id → contact_id` mit Backup-Spalte | -| P0-3 | Plugin-Upload deaktiviert | `app/routes/plugins.py` — `/upload` und `/install-url` return 403 mit `upload_disabled` / `install_url_disabled` | -| P0-4 | RLS repariert | `alembic/versions/0028_rls_force.py` — `FORCE ROW LEVEL SECURITY` + `WITH CHECK` auf allen Tenant-Tabellen | -| P0-5 | Plugin-Doppelregistrierung | `app/main.py` — Routes in `create_app()`, `lifespan()` nur aktiviert/deaktiviert, respektiert DB `active` Status, Migration-Fail deaktiviert Plugin | -| P0-6 | Persistent Volume | `docker-compose.yml` — `storage:/data/storage`, `pgdata`, `redisdata` Volumes | -| P1-1 | User/Tenant-Modell | `app/models/user.py` — `User` hat keine `tenant_id`/`role` mehr, `UserTenant` ist single source of truth, `email` global unique | -| P1-2 | Redis zentralisiert | `app/core/auth.py` — `init_redis()`/`get_redis()` Singleton, `init_job_pool()`/`close_job_pool()` | -| P1-3 | Worker ausgelagert | `prestart.sh` — nur Alembic + Uvicorn; separater `crm-worker` Container in `docker-compose.yml` | -| P1-4 | Transactional Outbox | `app/core/outbox.py`, `app/models/outbox.py`, `alembic/versions/0040_outbox.py` — `enqueue_outbox_event()` + `process_outbox_batch()` mit `FOR UPDATE SKIP LOCKED` | -| P1-5 | XSS-Stellen geschlossen | `HtmlBlock.tsx` + `SignatureManager.tsx` — `DOMPurify.sanitize()`; `ActionCardBlock.tsx` — URL-Validierung (nur `http:`/`https:`) | -| P1-6 | DMS lastfest | `app/plugins/builtins/dms/routes.py` — 1MB Chunked Streaming, SHA-256 Content-Hash | -| P1-7 | Permission-System | `app/core/permissions.py` — `permission_version` wird beim Cache-Lesen geprüft, `redis.scan()` statt `redis.keys()`, `require_write()` prüft spezifische Permissions | -| P1-8 | Password Reset | `app/services/auth_service.py` — ARQ Job `send_password_reset_email`, Token `used_at` Tracking | -| P1-9 | Metrics abgesichert | `app/routes/metrics.py` — `Depends(require_admin)` | -| P1-10 | Coolify-Doku & Config | `COOLIFY_SETUP.md` — Healthcheck `/api/v1/health`, JWT-Vars entfernt, CORS `:443`; `app/config.py` — `storage_path=/data/storage`, `session_cookie_secure=True`, Startup-Validierung; `docker-compose.yml` — Redis, Volumes, Healthcheck | -| P1-11 | Cross-Tenant FK | `alembic/versions/0036_cross_tenant_fk.py` — `UNIQUE (tenant_id, id)` + Composite FK `(tenant_id, contact_id)` auf `contactpersons` und `contact_merge_history` | -| P2-1 | Contact Model normalisiert | `alembic/versions/0039_contact_normalize.py` — `surfix→suffix`, `Float→Numeric(5,2)`, `JSON→JSONB`, `CHECK (0-100)`, Unique Constraints | -| P2-3 | Commands & Statusmaschinen | `app/commands/` (base, contact, calendar, dms, mail) + `app/core/state_machine.py` | -| P2-4 | SPA Path-Traversal | `app/main.py` — `os.path.abspath` Check + `".." in full_path` Blocking | - ---- - -## ⏳ Offene Items - -### P0-7: App von öffentlicher Domain nehmen - -**Status:** Operational — nicht aus Code verifizierbar - -**Problem:** Die App läuft unter `https://crm.media-on.de` und ist öffentlich erreichbar. - -**Maßnahme:** -1. **Sofort:** App von öffentlicher Domain nehmen oder IP-Whitelist/Basic Auth vorschalten -2. Mindestens P0-1 (Auth-Bypass ✅) und P0-3 (Plugin-Upload ✅) sind bereits behoben -3. Alternativ: VPN/Tunnel-Zugang statt öffentliche Domain - -**Aufwand:** 30 Minuten - ---- - -### P2-2: Plugin-Cross-Imports reduzieren - -**Status:** Offen — 228 direkte Cross-Imports zwischen Plugins - -**Problem:** 228 direkte `from app.plugins.builtins` Imports zwischen Plugins. Automatisierung importiert Modelle/Services von Kommunikation, Mail, Kalender. Verteilter Monolith ohne Modulgrenzen. - -**Maßnahme:** -1. Öffentliche Schnittstellen (Contracts) für jedes Modul definieren -2. Direkte Imports fremder Plugin-Modelle verbieten -3. Kommunikation nur über Events oder öffentliche Service-API -4. CI-Check: keine direkten Cross-Plugin-Imports - -**Aufwand:** 1-2 Wochen - ---- - -## Zusammenfassung - -| Priorität | Erledigt | Offen | Geschätzter Aufwand (offen) | -|---|---|---|---| -| P0 | 6/7 | 1 (operational) | 30 Minuten | -| P1 | 11/11 | 0 | — | -| P2 | 3/4 | 1 | 1-2 Wochen | -| **Total** | **20/22** | **2** | **~1-2 Wochen** | - -## Validierung nach jedem Fix - -- [ ] Python-Syntax-Check: `python -m py_compile app/**/*.py` -- [ ] pytest: `pytest tests/ -x` -- [ ] Frontend-Typecheck: `cd frontend && npx tsc --noEmit` -- [ ] Frontend-Build: `cd frontend && npx vite build` -- [ ] Manueller Smoke-Test: Login, Kontakt erstellen, DMS-Upload -- [ ] Cross-Tenant-Test: Datensatz aus Mandant A kann nicht aus Mandant B gelesen werden -- [ ] Deployment: Coolify Deploy + Healthcheck prüfen diff --git a/MASTER-PLAN.md b/MASTER-PLAN.md deleted file mode 100644 index b35552b..0000000 --- a/MASTER-PLAN.md +++ /dev/null @@ -1,754 +0,0 @@ -# LeoCRM — Master Plan: Umbau & Vollendung - -**Erstellt:** 2026-07-22 -**Status:** Draft — zur Freigabe -**Letzte Revision:** 2026-07-22 (gründliche Überprüfung nach Code-Tiefenanalyse) - ---- - -## Ausgangslage - -### Was bereits gut ist -- Backend: ~35.800 Zeilen, 12 Plugins, Multi-Tenant mit RLS, Rate Limiting, Audit Log -- Unified Contact Model: **BEREITS implementiert** (Migration 0021) — Contact mit type='company'|'person', ContactPerson als 1:N child (wie Rentman) -- Frontend: ~30.000 Zeilen, 27 Pages, 70 Components, i18n DE/EN, TanStack Query, TipTap -- Tests: ~17.300 Zeilen Backend-Tests, 38 Vitest-Dateien -- Docker: Multi-Stage-Build (Frontend+Backend in einem Container) -- Datenbank: PostgreSQL 16 als separater docker-compose Service -- WebSocket-Infrastruktur: Bereits im `kommunikation` Plugin vorhanden (`/api/v1/comm/ws`) — kann als Referenz für KI-UI-Steuerung dienen - -### Was fehlt oder nicht stimmt -- Frontend nutzt unified Contact Model nicht vollständig (keine Contact-Detail-Route, ContactPerson-Verwaltung fehlt in UI) -- **'company' als entity_type ist in 6 Plugins verankert** — muss zu 'contact' vereinheitlicht werden -- Plugin-UI-System fehlt (hartkodierte Routes statt dynamische Registry) -- Code-Splitting fehlt (alle 27 Pages im Main Bundle) -- E2E Tests fehlen komplett -- KI-UI-Steuerung fehlt -- Virtual Scrolling fehlt -- React Hook Form + Zod nicht überall -- hooks.ts ist Monolith (1.298 Zeilen) -- Fehlende Dependencies (lucide-react, date-fns) -- Plugin-Richtlinien fehlen - -### Wichtige Unterscheidung: 'company' hat zwei Bedeutungen -1. **entity_type='company'** in Plugins (entity_links, calendar, tags, mail) → referenziert eine Firma als Entität → **MUSS zu 'contact' werden** -2. **system_settings.company_*** Felder (company_name, company_street etc.) → CRM-Besitzer-Firmeninfo für Rechnungen → **BLEIBT wie es ist** -3. **CalendarType='company'** → Kalender-Typ (Firmenkalender) → kann bleiben oder zu 'organization' umbenannt werden (kosmetisch) - ---- - -## Architektur-Entscheidungen (freigegeben 2026-07-22) - -1. **KI-UI-Steuerung:** Keine Mausbewegung nötig. KI muss zu Kontakten springen und einen Kontakt öffnen können. Die UI muss das Ergebnis zeigen — Kontaktliste und spezieller Kontakt ausgewählt. Implementierungsweg (WebSocket, postMessage, etc.) ist offen, Hauptsache das Ergebnis wird in der UI sichtbar. -2. **Company-Routes:** Komplett entfernen. Keine deprecated-Routes, keine Redirects. Kontakte wie in Rentman — ein unified Contact-Modell, kein separates Company-Modell mehr. **Alle Plugin-Referenzen auf entity_type='company' müssen zu 'contact' migriert werden.** -3. **PostgreSQL:** Aktuell egal (Coolify-managed oder docker-compose). Reine Docker-Lösung soll später möglich sein. Keine Code-Änderung nötig — nur Konfiguration. -4. **S3-Storage:** Provider egal. Wichtig ist nur dass die Architektur es später ermöglicht. Bereits vorbereitet in config.py (STORAGE_BACKEND=s3). - ---- - -## Phasen-Plan - -### PHASE 0: Vorbereitung & Cleanup -**Ziel:** Codebasis bereinigen, Dependencies installieren, veraltete Dokumente aktualisieren - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 0.1 | Veraltete Planungsdokumente aktualisieren | 2h | `codebase-vs-requirements.md` neu schreiben (beschreibt alten Stand), `architecture.md` um Implementation-Status erweitern, `security-review-phase2.md` um 'Resolved' Markierungen ergänzen | -| 0.2 | `lucide-react` installieren + Icons migrieren | 4h | Inline SVGs durch lucide-react Icons ersetzen. Konsistente Icon-Bibliothek. | -| 0.3 | `date-fns` installieren + Datum-Formatierung | 3h | Alle `toLocaleDateString()` etc. durch date-fns ersetzen. Konsistente Datum-Formatierung. | -| 0.4 | `hooks.ts` aufteilen | 3h | 1.298 Zeilen aufteilen in `api/auth.ts`, `api/contacts.ts`, `api/settings.ts` etc. Generische Hooks bleiben in `hooks.ts`. Company-Hooks werden in Phase 1 entfernt, nicht aufgeteilt. | -| 0.5 | Store-Verzeichnis konsolidieren | 1h | `store/` und `stores/` zusammenführen. | -| 0.6 | Frontend-Bestandsanalyse als Dokument speichern | 1h | `frontend-gap-analysis.md` mit vollständiger Analyse. | -| 0.7 | UI-Design-Richtlinien erstellen | 6h | `docs/ui-design-guidelines.md` basierend auf bestehenden Plugin-Patterns (siehe unten). | -| 0.8 | Theme-Customization Backend | 4h | `system_settings` um Theme-Felder erweitern (primary_color, accent_color, font_family, border_radius). Neue Alembic-Migration. API-Endpoints zum Lesen/Schreiben der Theme-Settings. | -| 0.9 | Theme-Customization Frontend | 6h | `SettingsTheme.tsx` Seite mit Color-Picker, Font-Auswahl, Live-Preview. Tailwind-CSS-Variablen dynamisch aus API-Settings überschreiben. Dark-Mode-Toggle. Theme wird beim App-Start geladen und angewendet. | -| 0.10 | RBAC-Audit & Plugin-Permissions nachrüsten | 6h | 4 Plugins haben `permissions=[]` (calendar, dms, entity_links, tags) → keine Rechte-Prüfung! Pro Plugin passende Permissions definieren und in Manifest eintragen. Routes mit `require_permission()` absichern. Siehe Details unten. | -| 0.11 | LiteLLM-Cleanup & alte llm_client.py migrieren | 3h | LiteLLM ist **BEREITS** in ai_assistant und ai_proactive integriert (`litellm.acompletion()`). Nur die alte `llm_client.py` (Copilot) nutzt noch httpx direkt. Diese auf LiteLLM umstellen oder entfernen. System-Prompt in llm_client.py referenziert noch `/api/v1/companies` → auf Contacts umstellen. | -| 0.12 | KI-Agent-Framework in Plugin-Richtlinien dokumentieren | 2h | PydanticAI + tool_registry existieren bereits. In `docs/plugin-development-guide.md` dokumentieren: Wie Plugins KI-Agenten, Tools und LLM-Funktionen nutzen. Plugin-Manifest um `agent_capabilities` Feld erweitern. | -| 0.13 | Heartbeat konfigurierbar machen | 3h | Heartbeat-Intervall, Aktivierung, Ziel-Room in ProactiveSettings (DB) speichern. Settings-UI für Heartbeat-Konfiguration. | -| 0.14 | Unified Search: Field-Level RBAC nachrüsten | 4h | Search-Provider prüfen aktuell KEINE Feld-Level-Permissions. Nutzer mit `search:read` sieht alle Felder. Provider müssen `resolved_perms` prüfen und `hidden` Felder ausblenden. `to_search_result()` um Permission-Filter ergänzen. | -| 0.15 | Undo/History-System für CRUD-Operationen | 8h | Globale Undo-History: Jede CRUD-Aktion (Create/Update/Delete) wird mit Snapshot in `entity_history` Tabelle gespeichert. User kann Änderungen rückgängig machen oder zu früherer Version zurückkehren. Nutzt bestehenden Audit-Log als Basis. Frontend: Undo-Button + History-Viewer pro Entity. | -| 0.16 | Storage Backend implementieren (S3-Support) | 8h | Architecture.md beschreibt abstract StorageBackend (local/S3), aber **existiert NICHT im Code**. Attachments nutzen hardcoded `/data/uploads`. Storage-Klasse erstellen: `LocalStorage` + `S3Storage`. Config um `STORAGE_BACKEND`, `S3_ENDPOINT`, `S3_BUCKET`, `S3_ACCESS_KEY`, `S3_SECRET_KEY` erweitern. DMS und Attachments auf Storage-Backend umstellen. .env.example um S3-Variablen ergänzen. | -| 0.17 | Import/Export an unified Contact Model anpassen | 4h | Import/Export nutzt alte Feldnamen (`first_name`, `last_name`, `mobile`, `position`, `department`). Auf unified Contact-Felder umstellen (`firstname`, `surname`, `phone_1`, `email_1`, etc.). Company-Import auf Contact mit type='company' umstellen. | -| 0.18 | .gitignore & Config-Cleanup | 2h | `.gitignore` hat `webui/` statt `frontend/` — frontend/node_modules und frontend/dist werden nicht ignoriert! Korrigieren. `python-jose` (JWT) aus requirements.txt entfernen — Code nutzt Session-Auth. `pyproject.toml` Python-Version auf 3.12 aktualisieren. `.env.docker.example` JWT-Variablen entfernen. **.env aus Git entfernen** (ist committet aber sollte nicht sein). `dump.rdb` und `test.txt` aus Repo löschen. `frontend/dist/` aus Git entfernen (sollte nicht committet sein). | -| 0.19 | Mail-Salt Security-Fix | 2h | `mail/services.py` hat hardcoded salt `b"leocrm-mail-salt"` für Passwort-Verschlüsselung. Salt sollte random pro Account sein. Fix: Random salt generieren und mit encrypted_password zusammen speichern. DB-Migration für bestehende Accounts. | -| 0.20 | AGPL-Lizenzen durch kommerziell nutzbare Alternativen ersetzen | 6h | **PyMuPDF** (AGPL-3.0) → ersetzen durch `pypdf` (BSD). Text-Extraktion in unified_search anpassen. **OnlyOffice** (AGPL-3.0) → ersetzen durch **Collabora Online** (LGPL/MPL). DMS Edit-Sessions auf Collabora umstellen. `requirements.txt`, `Dockerfile`, `docker-compose.yml`, `architecture.md` aktualisieren. DMS Plugin `OnlyOfficeConfig` → `CollaboraConfig`. Frontend DMS-Komponenten anpassen. Lizenz-Datei (`LICENSE`) und `THIRD_PARTY_LICENSES.md` erstellen. | - -**Phase 0 Gesamt: ~77h** - -### UI-Design-Richtlinien (Task 0.7) - -Basierend auf Analyse der bestehenden Plugins (Calendar, Mail, DMS, Contacts): - -**Layout-Patterns:** -- **3-Spalten-Explorer-Layout** (Tree | Liste/Explorer | Detail) — verwendet von Calendar, Mail, DMS -- **ResizablePanel** für drag-to-resize Spalten — bereits implementiert -- **PluginToolbar** für Plugin-Aktionen (oben) — bereits implementiert -- **Modal** für Formulare (Create/Edit/Delete-Bestätigung) — bereits implementiert -- **EmptyState** für leere Listen — bereits implementiert -- **LoadingState/Skeleton** für Lade-Zustände — bereits implementiert - -**Farbsystem (Tailwind Design Tokens):** -- `primary` (Blau #2563eb) — Hauptaktionen, aktive Zustände -- `secondary` (Slate #64748b) — Text, Borders, Hintergründe -- `accent` (Fuchsia #d946ef) — Hervorhebungen, Info-Badges -- `danger` (Rot #dc2626) — Löschen, Fehler -- `warning` (Amber #f59e0b) — Warnungen -- `success` (Grün #16a34a) — Erfolg, Bestätigungen -- Jede Farbe mit 50-900 Schattierungen -- **Dark Mode** via `darkMode: 'class'` — CSS-Variablen in `:root` und `.dark` - -**Typografie:** -- Font: `Inter` (system-ui fallback) -- Mono: `JetBrains Mono` für Code/Daten -- Größen: xs (0.75rem) bis 4xl (2.25rem) -- Zeilenhöhen definiert pro Größe - -**Komponenten-Konventionen:** -- **Button**: 4 Varianten (primary/secondary/danger/ghost), 3 Größen (sm/md/lg), `min-h-touch` (44px), `focus-visible:ring-2` -- **Card**: Titel + Beschreibung + Actions (header), Body, optional Footer (bg-secondary-50) -- **Badge**: 7 Varianten (default/primary/success/warning/danger/info/secondary), optional dot -- **Input/Select**: `focus-ring` Klasse, `border-secondary-200`, `rounded-md` -- **Modal**: `size` prop (sm/md/lg/xl), `ConfirmDialog` für Bestätigungen -- **Table/DataGrid**: TanStack Table, ARIA-labels auf sortierbare Headers -- **Toast**: `useToast()` Hook für Benachrichtigungen - -**Spacing & Layout:** -- Standard-Padding: `px-6 py-4` (Card body), `p-4` (Panel) -- Gap: `gap-2` (Buttons), `gap-4` (Sections), `gap-6` (Columns) -- Border-Radius: `rounded-md` (0.5rem) Standard, `rounded-lg` (0.75rem) für Cards -- Shadow: `shadow-sm` (Cards), `shadow-md` (Dropdowns), `shadow-lg` (Modals) - -**Accessibility (bereits implementiert):** -- `focus-ring` Klasse: `focus-visible:ring-2 focus-visible:ring-primary-500` -- `btn-touch` Klasse: `min-h-touch min-w-touch` (44px) -- `sr-only` und `sr-only-focusable` Klassen -- `prefers-reduced-motion` Media Query -- `aria-hidden="true"` auf dekorativen SVGs -- `aria-label` auf interaktiven Elementen ohne sichtbaren Text - -**Plugin-UI-Patterns (für neue Plugins):** -- Jede Plugin-Seite folgt dem 3-Spalten-Layout (wenn anwendbar) -- PluginToolbar für Aktionen (Create, Import, Export, etc.) -- Plugin-Settings als eigene Settings-Sub-Seite -- Plugin-Detail-Tabs (z.B. "Dateien" bei Contact-Detail) -- Konsistente EmptyState-Komponente wenn keine Daten -- Konsistente LoadingState/Skeleton-Komponente beim Laden -- Toast für Erfolg/Fehler-Meldungen nach Aktionen -- ConfirmDialog vor destruktiven Aktionen - -**Was im Design-Guide dokumentiert wird:** -1. Farbsystem mit Verwendungsregeln (wann welche Farbe) -2. Typografie-Hierarchie (Überschriften, Body-Text, Labels) -3. Layout-Patterns (3-Spalten, Modal, Settings-Tree) -4. Komponenten-Verwendung (welche Komponente für was) -5. Spacing & Sizing Konventionen -6. Accessibility-Regeln -7. Dark-Mode-Regeln -8. Plugin-UI-Patterns für neue Plugins -9. Do's & Don'ts -10. Code-Beispiele aus bestehenden Plugins - -### RBAC-Audit & Plugin-Permissions (Task 0.10) - -**Problem:** 4 Plugins haben `permissions=[]` im Manifest → keine Rechte-Prüfung auf ihren Routes: - -| Plugin | Aktuell | Muss definiert werden | -|---|---|---| -| **calendar** | `permissions=[]` | `calendar:read`, `calendar:write`, `calendar:delete`, `calendar:share`, `calendar:admin` | -| **dms** | `permissions=[]` | `dms:read`, `dms:write`, `dms:delete`, `dms:share`, `dms:admin` | -| **entity_links** | `permissions=[]` | `entity_links:read`, `entity_links:write`, `entity_links:delete` | -| **tags** | `permissions=[]` | `tags:read`, `tags:write`, `tags:delete`, `tags:admin` | - -**Was zu tun ist:** -1. Pro Plugin passende Permissions im Manifest definieren -2. Alle Plugin-Routes mit `require_permission()` absichern -3. Permission-Registry registriert Plugin-Permissions automatisch beim Aktivieren -4. Admin kann Permissions in Rollen-Editor zuweisen -5. Tests: User ohne Permission → 403, User mit Permission → 200 - -**Zusätzlich in Phase 1 (Permission-Registry-Cleanup):** -- `companies:read/write/delete` aus `CORE_PERMISSIONS` entfernen (wird zu `contacts:read/write/delete`) -- `CORE_FIELD_DEFINITIONS` aktualisieren: alte Felder (`first_name`, `last_name`, `mobile`, `position`, `department`, `linkedin_url`) durch unified Contact-Felder ersetzen (`firstname`, `surname`, `phone_1`, `email_1`, etc.) -- `companies` Field-Definitions entfernen - -### LiteLLM-Integration (Task 0.11) - -**Problem:** Aktuelle `llm_client.py` spricht nur OpenAI-compatible API direkt via httpx. Keine Unterstützung für Anthropic, Google, lokale Modelle etc. - -**Lösung:** LiteLLM als unified LLM-Interface integrieren. - -**Was LiteLLM bietet:** -- 100+ LLM-Provider über eine einheitliche API (OpenAI, Anthropic, Google, Azure, AWS Bedrock, Ollama, etc.) -- Konsistente Request/Response-Formate -- Streaming-Support -- Fallback/Routing-Regeln -- Cost-Tracking -- Rate-Limiting - -**Was zu tun ist:** -1. `litellm` als Python-Dependency hinzufügen -2. `llm_client.py` auf LiteLLM umstellen: `litellm.acompletion()` statt direktem httpx-Call -3. Konfiguration via Env-Vars: `AI_MODEL`, `AI_API_KEY`, `AI_API_BASE` (bleiben gleich), plus `AI_PROVIDER` (neu: openai/anthropic/google/ollama/etc.) -4. AI Assistant Plugin nutzt LiteLLM für Multi-Provider-Support -5. AI Proactive Plugin nutzt LiteLLM für Suggestions -6. Zukünftige Plugins können LiteLLM einfach nutzen — einheitliches Interface -7. Mock-Mode für Tests beibehalten (wenn kein API-Key gesetzt) -8. Plugin-Entwickler-Richtlinien: Wie man LiteLLM in neuen Plugins nutzt - -**Architektur:** -``` -Plugin (ai_assistant, ai_proactive, zukünftige) - ↓ -LiteLLM (unified LLM interface) - ↓ -Provider (OpenAI, Anthropic, Google, Ollama, ...) -``` - -**Vorteil für zukünftige Plugins:** -- Ein Plugin kann LLM-Funktionen nutzen ohne sich um den Provider zu kümmern -- Admin kann Provider in Settings konfigurieren -- KI-Modelle können ausgetauscht werden ohne Code-Änderung - ---- - -### PHASE 1: Unified Contact Model — Vollendung (Backend + Frontend) -**Ziel:** 'company' als separates Konzept komplett entfernen. Alles ist 'contact' mit type='company'|'person'. Wie Rentman. - -#### 1A: Backend — Company-Routes & Services entfernen - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 1.1 | `app/routes/companies.py` entfernen | 1h | 303 Zeilen. Router aus `main.py`/`routes/__init__.py` austragen. | -| 1.2 | `app/services/company_service.py` entfernen | 1h | 273 Zeilen. Importe aus `services/__init__.py` entfernen. | -| 1.3 | `app/models/company.py` entfernen | 1h | Backward-compat shim. Importe überall auf `Contact` umstellen. | -| 1.4 | `app/schemas/company.py` entfernen | 1h | CompanyCreate, CompanyUpdate, CompanyResponse etc. | -| 1.5 | `app/ai/action_mapper.py` aktualisieren | 3h | Company-Intents (create_company, delete_company, update_company, list_company) auf Contact-API umstellen. Regex-Patterns anpassen. | -| 1.6 | `app/workflows/engine.py` aktualisieren | 1h | Event `company.created` → `contact.created`. Workflow-Trigger anpassen. | -| 1.7 | `app/core/worker.py` aktualisieren | 1h | `index_company` Referenzen → `index_contact`. | -| 1.8 | `app/core/seeds.py` prüfen/aktualisieren | 1h | Falls Company-Seed-Daten existieren, auf Contact mit type='company' umstellen. | - -**1A Gesamt: ~10h** - -#### 1B: Backend — Plugins von entity_type='company' befreien - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 1.9 | **entity_links Plugin** aktualisieren | 4h | `entity_type` Pattern von `^(company|contact)$` → `^contact$`. `company_router` entfernen. `on_company_deleted` → `on_contact_deleted`. Event `company.deleted` → `contact.deleted`. DB-Migration: bestehende EntityLinks mit entity_type='company' auf 'contact' migrieren. | -| 1.10 | **unified_search Plugin** aktualisieren | 6h | `CompanySearchProvider` → wird zu `ContactSearchProvider` oder bleibt als Provider für type='company' Kontakte. `index_company` → `index_contact`. Events `company.created/updated` → `contact.created/updated`. `search_engine.py` Mapping `"company" → "contacts"` anpassen. `jobs.py` aktualisieren. | -| 1.11 | **calendar Plugin** aktualisieren | 3h | `entity_type` Pattern von `^(company|contact)$` → `^contact$`. CalendarEntryLink entity_type anpassen. DB-Migration: bestehende Links migrieren. CalendarType='company' kann bleiben (Kalender-Typ, nicht Entity-Referenz). | -| 1.12 | **tags Plugin** aktualisieren | 3h | `entity_type` Pattern von `^(company|contact|file|folder)$` → `^(contact|file|folder)$`. DB-Migration: bestehende Tag-Assignments mit entity_type='company' auf 'contact' migrieren. | -| 1.13 | **mail Plugin** aktualisieren | 4h | `mail.company_id` Spalte → `mail.contact_id` (DB-Migration). Routes, Schemas, Services aktualisieren. `company_id` Referenzen in Frontend-API-Modul. | -| 1.14 | **test_sample Plugin** aktualisieren | 1h | `company.created` Event → `contact.created`. Test-Plugin ist Referenz für Plugin-Entwicklung. | -| 1.15 | **Event-Namen vereinheitlichen** | 2h | Alle `company.created/updated/deleted` Events → `contact.created/updated/deleted`. Event-Publisher in contact_service.py prüfen. | -| 1.16 | **DB-Migration: entity_type 'company' → 'contact'** | 3h | Alembic-Migration: UPDATE entity_links SET entity_type='contact' WHERE entity_type='company'. UPDATE tag_assignments SET entity_type='contact' WHERE entity_type='company'. UPDATE calendar_entry_links SET entity_type='contact' WHERE entity_type='company'. ALTER TABLE mails RENAME COLUMN company_id TO contact_id. | -| 1.17 | **Backend-Tests aktualisieren** | 4h | Alle Tests die Company-Routes oder entity_type='company' referenzieren umstellen. `test_companies.py` entfernen oder zu Contact-Tests umschreiben. | -| 1.18 | **Permission-Registry-Cleanup** | 3h | `companies:read/write/delete` aus `CORE_PERMISSIONS` entfernen. `CORE_FIELD_DEFINITIONS` aktualisieren: alte Felder durch unified Contact-Felder ersetzen. `companies` Field-Definitions entfernen. | -| 1.19 | **Addresses entity_type='company' → 'contact'** | 2h | `address_service.py` `VALID_ENTITY_TYPES` von `{"company", "contact"}` → `{"contact"}`. `address.py` Model anpassen. DB-Migration: bestehende Adressen mit entity_type='company' auf 'contact' migrieren. | -| 1.20 | **conftest.py aktualisieren** | 2h | `conftest.py` importiert `Company` und `CompanyContact` aus alten Modellen. Auf unified Contact Model umstellen. Test-Fixtures anpassen. | - -**1B Gesamt: ~33h** - -#### 1C: Frontend — Unified Contact UI - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 1.18 | Contact-Detail-Route hinzufügen | 2h | Route `/contacts/:id` in `routes/index.tsx`. `ContactDetail.tsx` (372 Zeilen) existiert bereits als Komponente. | -| 1.19 | ContactList mit Type-Filter (company/person) | 4h | `ContactsList.tsx` (445 Zeilen) um Type-Filter erweitern. Tabs oder Toggle: "Alle | Firmen | Personen". | -| 1.20 | ContactDetail um ContactPerson-Verwaltung erweitern | 8h | Bei type='company': Ansprechpartner-Liste, Ansprechpartner hinzufügen/bearbeiten/löschen. ContactPerson API-Hooks in Frontend. | -| 1.21 | ContactEditModal für beide Types | 6h | Formular je nach type unterschiedlich: company → name, person → firstname/surname. Adressen (mailing/visit/invoice). | -| 1.22 | Company-Hooks aus `hooks.ts` entfernen | 2h | `useCompanies`, `useCompany`, `useCreateCompany`, `useUpdateCompany`, `useDeleteCompany`, `useCompanyExport`, `useCompanyImport` entfernen. Company-Interface entfernen. | -| 1.23 | Frontend Type-Definitions aktualisieren | 2h | `calendar.ts`: entity_type 'company' → 'contact'. `tags.ts`: EntityType 'company' entfernen. `search.ts`: type 'company' → 'contact'. `mail.ts`: company_id → contact_id. | -| 1.24 | Dashboard.tsx aktualisieren | 1h | `useUnifiedContacts(1, 1, undefined, 'company')` → `useUnifiedContacts(1, 1, undefined, 'company')` (type-Filter bleibt, ist jetzt Contact type nicht Company entity). | -| 1.25 | GlobalSearchResults.tsx aktualisieren | 2h | Search result type 'company' → 'contact'. Grouping, Icons, Labels anpassen. | -| 1.26 | ContactFolderTree in ContactList integrieren | 4h | Ordner-Baum links, Kontaktliste rechts. Drag & Drop Kontakte in Ordner. | -| 1.27 | React Hook Form + Zod in ContactEditModal | 3h | Strukturierte Validierung für alle Contact-Felder. | -| 1.28 | Frontend-Tests aktualisieren | 4h | Tests für Contact-Detail, ContactEditModal, ContactPerson-Verwaltung. Company-Test-Referenzen entfernen. | - -**1C Gesamt: ~38h** - -**Phase 1 Gesamt: ~81h** (vorher 33h — unterschätzt um 48h!) - ---- - -### PHASE 2: Code-Splitting & Performance -**Ziel:** Frontend lädt nur was nötig ist. Virtual Scrolling überall. - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 2.1 | React.lazy + Suspense für alle Routes | 4h | Alle Page-Imports in `routes/index.tsx` auf `React.lazy()` umstellen. `` mit Loading-Fallback. | -| 2.2 | `@tanstack/react-virtual` installieren | 1h | Dependency hinzufügen. | -| 2.3 | Virtual Scrolling in DataGrid | 6h | `DataGrid.tsx` um Virtual Scrolling erweitern. Nur sichtbare Zeilen rendern. | -| 2.4 | Virtual Scrolling in MailList | 4h | `MailList.tsx` um Virtual Scrolling erweitern. | -| 2.5 | Virtual Scrolling in ContactList | 4h | `ContactList.tsx` um Virtual Scrolling erweitern. | -| 2.6 | Virtual Scrolling in allen anderen Listen | 4h | AuditLog, Calendar Entries, DMS FileGrid, etc. | -| 2.7 | Bundle-Analyse & Optimierung | 2h | `vite-bundle-visualizer` prüfen, manuelle Chunks für große Dependencies. | - -**Phase 2 Gesamt: ~25h** - ---- - -### PHASE 3: Plugin-UI-System (WordPress-Style) -**Ziel:** Dynamisches Plugin-UI-Loading. Plugins registrieren sich selbst. - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 3.1 | Plugin-Manifest-Frontend-Endpoint | 4h | Backend-Endpoint `GET /api/v1/plugins/active-manifests` liefert alle aktiven Plugin-Manifeste mit UI-Definitionen (routes, menu_items, detail_tabs, settings_pages, dashboard_widgets). | -| 3.2 | `PluginRegistry.tsx` erstellen | 8h | Fetcht aktive Plugin-Manifeste beim App-Start. Registriert Routes, Menu-Items, Detail-Tabs, Settings-Pages dynamisch. | -| 3.3 | `PluginLoader.tsx` erstellen | 6h | Lazy-loaded Plugin-Komponenten via `React.lazy()`. Suspense-Boundaries pro Plugin. Error-Boundary falls Plugin nicht lädt. | -| 3.4 | Sidebar dynamisch aus Plugin-Manifesten | 4h | Sidebar rendert Menu-Items aus Plugin-Registry statt hartkodierte Items. | -| 3.5 | Settings-Baum dynamisch aus Plugin-Manifesten | 4h | Settings-Pages werden dynamisch aus Plugin-Manifesten generiert. | -| 3.6 | Detail-Tabs dynamisch (Contact-Detail) | 4h | Plugin-Detail-Tabs (z.B. "Dateien", "E-Mails", "Kalender") werden dynamisch gerendert. | -| 3.7 | Plugin-Routen aus hartkodiertem Router entfernen | 4h | Statische Plugin-Imports aus `routes/index.tsx` entfernen. Alles über PluginRegistry. | -| 3.8 | Plugin-Entwickler-Richtlinien erstellen | 8h | `docs/plugin-development-guide.md`: Manifest-Format, Lifecycle, UI-Registrierung, Event-Bus, Migration-Runner, Service-Container, Beispiele, Do's & Don'ts, Testing-Guide. | -| 3.9 | Plugin-Templates / Boilerplate | 4h | `templates/plugin-template/`: Minimal-Plugin als Startpunkt für neue Plugins. Mit Manifest, Routes, Models, Schemas, Migration, Tests. | -| 3.10 | Tests für Plugin-UI-System | 4h | Vitest-Tests für PluginRegistry, PluginLoader, dynamische Sidebar/Settings. | -| 3.10b | Plugin-Install-System | 8h | Plugins einfach installierbar machen: ZIP-Upload, URL-Install, Plugin-Marketplace-Integration. Plugin-Upload-Endpoint, Validierung (Manifest prüfen, tenant_id-Check, Security-Scan), automatische Migration bei Install. Install-UI in SettingsPlugins.tsx. | - -**Phase 3 Gesamt: ~58h** - ---- - -### PHASE 3.5: Automation & Agents Plugin -**Ziel:** Zentrale Oberfläche für Automatisierungen und selbst-arbeitende KI-Agenten. Plugins können Agenten und Automation-Templates mitbringen. - -**Architektur:** -``` -┌─────────────────────────────────────────────┐ -│ Automation & Agents UI │ -│ ┌─────────────┐ ┌─────────────────────┐ │ -│ │ Automation │ │ Agent Builder │ │ -│ │ Builder │ │ - Agent definieren │ │ -│ │ - Trigger │ │ - Tools auswählen │ │ -│ │ - Schedule │ │ - LLM-Modell wählen │ │ -│ │ - Conditions │ │ - Heartbeat setzen │ │ -│ │ - Actions │ │ - Proaktiv/Reaktiv │ │ -│ └─────────────┘ └─────────────────────┘ │ -├─────────────────────────────────────────────┤ -│ Cron-Scheduler │ Workflow-Timeouts │ HB │ -├─────────────────────────────────────────────┤ -│ Plugins bringen mit: │ -│ - agent_definitions (Agent-Templates) │ -│ - automation_templates (Automation-Tpl) │ -│ - cron_jobs (periodische Tasks) │ -│ - heartbeat_configs │ -└─────────────────────────────────────────────┘ -``` - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 3.11 | Plugin-Manifest um Agent/Automation-Felder erweitern | 4h | Manifest um `agent_definitions`, `automation_templates`, `cron_jobs`, `heartbeat_configs` erweitern. Plugins deklarieren was sie mitbringen. | -| 3.12 | Cron-Scheduler Backend | 6h | ARQ-basierter Scheduler für periodische Tasks. Cron-Expressions (z.B. `0 8 * * *` = täglich 8 Uhr). Scheduler liest aktive Cron-Jobs aus DB und enqueued sie. Ersetzt hartkodierten Heartbeat. | -| 3.13 | Workflow-Timeout-Worker | 4h | ARQ-Job der regelmäßig Workflow-Instanzen mit abgelaufenem `timeout_at` prüft. Bei Timeout: Status auf `cancelled`, Notification an Initiator. | -| 3.14 | Agent Builder Backend | 8h | API für Agent-Definitionen: Name, Beschreibung, LLM-Modell, Tools (aus tool_registry), System-Prompt, Heartbeat-Intervall, Proaktiv/Reaktiv-Modus. Agent-Definitionen in DB gespeichert. | -| 3.15 | Automation Builder Backend | 6h | API für Automation-Definitionen: Trigger (Event/Schedule/Manual), Conditions, Actions (API-Call/Notification/Workflow-Start). Automation-Definitionen in DB gespeichert. | -| 3.16 | Automation Execution Engine | 6h | Engine die Automations ausführt: Event-Trigger → Conditions prüfen → Actions ausführen. Nutzt Event-Bus für Event-Trigger, Cron-Scheduler für Schedule-Trigger. | -| 3.17 | Agent Runner | 8h | Führt Agenten aus: Proaktiv (Heartbeat-getriggert, sammelt Kontext, generiert Vorschläge) oder Reaktiv (auf Event/Message, reagiert). Nutzt LiteLLM + tool_registry + PydanticAI. | -| 3.18 | Automation & Agents UI — Automation Builder | 8h | Visueller Builder für Automations: Trigger auswählen, Conditions definieren, Actions zusammenstellen. Drag & Drop oder Form-basiert. Live-Preview. | -| 3.19 | Automation & Agents UI — Agent Builder | 8h | Visueller Builder für Agenten: Name, Modell, Tools, System-Prompt, Heartbeat. Test-Run Button. Agent-Liste mit Status (aktiv/inaktiv). | -| 3.20 | Automation & Agents UI — Dashboard | 4h | Übersicht: Aktive Automations, Aktive Agenten, Letzte Ausführungen, Logs, Fehler. Heartbeat-Status pro Agent. | -| 3.21 | Plugin-Beiträge registrieren | 4h | Wenn Plugin aktiviert wird: Agent-Definitionen, Automation-Templates, Cron-Jobs aus Manifest registrieren. Bei Deaktivierung: entfernen. | -| 3.22 | Heartbeat-Verwaltung migrieren | 3h | Hartkodierten Heartbeat aus ai_proactive in Automation & Agents Plugin migrieren. Heartbeat wird zu einem konfigurierbaren Cron-Job. | -| 3.23 | Settings für Automation & Agents | 3h | Einstellungen: Default-LLM-Modell für Agenten, Heartbeat-Default-Intervall, Max-Concurrent-Agents, Log-Level. | -| 3.24 | Tests für Automation & Agents | 6h | Tests für Cron-Scheduler, Workflow-Timeouts, Agent Runner, Automation Engine, Plugin-Beiträge. | -| 3.25 | Agent- & Automation-Logs | 4h | Jede Agent-Ausführung und Automation-Ausführung wird geloggt: Start, Ende, Status, Dauer, Ergebnis, Fehler. Log-Viewer in Dashboard UI. Historie pro Agent/Automation. | -| 3.26 | RBAC für Automation & Agents | 3h | Permissions definieren: `automation:read`, `automation:write`, `automation:delete`, `automation:execute`, `agents:read`, `agents:write`, `agents:delete`, `agents:execute`. Nur Admin/Editor dürfen Agenten/Automations erstellen. | -| 3.27 | Dry-Run / Test-Modus | 3h | Automations und Agenten können im Dry-Run getestet werden: Führt Conditions aus, zeigt was passieren würde, aber führt keine destruktiven Actions aus. Test-Button in Builder UI. | -| 3.28 | Agent Rate-Limiting & Safety | 3h | Max-Ausführungen pro Agent pro Stunde. Max-Dauer pro Ausführung. Auto-Stop bei Endlosschleife (wenn Agent dieselbe Action 5x hintereinander ausführt). Budget-Limit pro Agent (LiteLLM Cost-Tracking). | -| 3.29 | Plugin-Beitrags-Konfliktlösung | 2h | Wenn zwei Plugins denselben Agent-Namen/Templat-Namen mitbringen: Plugin-Name als Prefix (`mail.mail_sorter` statt `mail_sorter`). Dedup-Logik bei Registrierung. | -| 3.30 | Agent-zu-Agent-Kommunikation | 8h | Agenten können Nachrichten an andere Agenten senden. Nutzt kommunikation Plugin-Infrastruktur (WebSocket, Rooms). Agent-Message-Router: Agent A sendet `{to: 'mail_sorter', message: 'Neuer Termin gefunden'}`. Empfänger-Agent reagiert. Agent-Chatrooms in Dashboard sichtbar. | -| 3.31 | Versionshistorie für Agenten & Automations | 4h | Jede Änderung an Agent/Automation erstellt neue Version. Alte Versionen können wiederhergestellt werden. Versions-Diff in UI. `agent_versions` und `automation_versions` Tabellen. | -| 3.32 | MiniApps: Plugin-MiniApps im Chat | 6h | **Bereits implementiert:** `MiniAppRegistry`, `MiniAppDef`, Routes (`GET /miniapps`, `POST /conversations/{id}/miniapps`), `MiniAppBlock.tsx` Frontend. **Was fehlt:** Plugin-Manifest um `miniapps` Feld erweitern (Plugins deklarieren welche MiniApps sie mitbringen). MiniApp-Builder UI (visuell MiniApps erstellen). MiniApp-Store in Settings. Dokumentation in Plugin-Entwickler-Richtlinien. | - -**Phase 3.5 Gesamt: ~105h** - -**Was Plugins mitbringen können:** -- **Agent-Definitionen:** Ein Plugin kann vordefinierte Agenten mitbringen (z.B. Mail-Plugin bringt "E-Mail-Sortier-Agent" mit) -- **Automation-Templates:** Ein Plugin kann Automation-Vorlagen mitbringen (z.B. Calendar-Plugin bringt "Terminerinnerung 24h vorher" mit) -- **Cron-Jobs:** Ein Plugin kann periodische Tasks deklarieren (z.B. Mail-Plugin: "IMAP-Sync alle 15 Minuten") -- **Heartbeat-Configs:** Ein Plugin kann Heartbeat-Konfigurationen mitbringen - -**Beispiel: Mail-Plugin bringt Agent mit** -```json -{ - "agent_definitions": [{ - "name": "mail_sorter", - "display_name": "E-Mail-Sortier-Assistent", - "description": "Sortiert eingehende E-Mails automatisch nach Regeln", - "model": "ollama/deepseek-v4-flash", - "tools": ["mail.read", "mail.move", "mail.label"], - "system_prompt": "Du sortierst E-Mails...", - "mode": "reactive", - "trigger_event": "mail.received" - }] -} -``` - -**Beispiel: Calendar-Plugin bringt Automation mit** -```json -{ - "automation_templates": [{ - "name": "appointment_reminder", - "display_name": "Terminerinnerung 24h vorher", - "trigger": {"type": "schedule", "cron": "0 8 * * *"}, - "conditions": [{"field": "entry.start_at", "operator": "lt", "value": "now + 24h"}], - "actions": [{"type": "notification", "title": "Terminerinnerung", "body": "Morgen: ${entry.title}"}] - }] -} -``` - ---- - -### PHASE 4: KI-UI-Steuerung -**Ziel:** KI-Agent kann UI steuern — Kontakte öffnen, Filter setzen, navigieren. User sieht das Ergebnis in der UI. - -**Wichtig:** Bestehende WebSocket-Infrastruktur im `kommunikation` Plugin (`/api/v1/comm/ws`, `websocket_manager.py`) kann als Referenz dienen. - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 4.1 | UI-Command-Protokoll definieren | 4h | JSON-Protokoll für UI-Befehle: `{action: 'navigate', path: '/contacts/123'}`, `{action: 'filter', entity: 'contacts', filter: {type: 'company'}}`, `{action: 'open_contact', id: '...'}`. | -| 4.2 | WebSocket-Endpoint für KI-UI-Steuerung | 6h | Backend-WebSocket `/ws/ai-ui-control`. Authentifiziert via Session. KI-Agent sendet Commands, Frontend empfängt. Basiert auf bewährter WebSocket-Infrastruktur aus kommunikation Plugin. | -| 4.3 | Frontend `useAIUIControl` Hook | 6h | WebSocket-Client im Frontend. Empfängt Commands und führt sie aus. Nutzt React Router, TanStack Query, Zustand Stores. | -| 4.4 | Command: Navigate | 2h | `useNavigate()` für Route-Wechsel. KI kann zu jeder Seite navigieren. | -| 4.5 | Command: Filter setzen | 4h | URL-Search-Params setzen für Listen-Filter. KI kann Filter setzen (z.B. "Zeige nur Firmen in Berlin"). | -| 4.6 | Command: Contact öffnen | 3h | Navigate zu `/contacts/:id` + Detail-Daten laden. KI kann Kontakt öffnen und User sieht ihn. | -| 4.7 | Command: Modal öffnen/schließen | 3h | EditModal, CreateModal etc. per Command steuerbar. | -| 4.8 | Command: Tab wechseln | 2h | Detail-Tabs (Dateien, E-Mails, Kalender) per Command wechseln. | -| 4.9 | Command: Settings ändern | 3h | System-Settings, User-Preferences per UI-Command ändern. Wird in UI sichtbar. | -| 4.10 | UI-Action-Feedback an KI | 4h | Frontend sendet Bestätigung zurück: `{action: 'navigate', status: 'success', current_path: '/contacts/123'}`. KI weiß, dass Command ausgeführt wurde. | -| 4.11 | Visuelle KI-Indikation | 3h | Wenn KI eine Aktion ausführt: kurzer Highlight-Effekt oder Toast "KI führt Aktion aus...". User sieht dass KI agiert. | -| 4.12 | Tests für KI-UI-Steuerung | 4h | Vitest-Tests für Command-Protokoll, useAIUIControl Hook, Command-Ausführung. | - -**Phase 4 Gesamt: ~44h** - ---- - -### PHASE 5: API-Vollständigkeit & KI-Testbarkeit -**Ziel:** App komplett per API steuerbar. KI kann selbstständig testen und Updates einspielen. - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 5.1 | API-Audit: Alle UI-Funktionen per API erreichbar | 8h | Systematische Prüfung: Jede UI-Aktion hat einen API-Endpoint. Fehlende Endpoints identifizieren und implementieren. Sidebar-Zustand, Tab-Auswahl, Filter-Zustand per API speichern/laden. | -| 5.2 | User-Preferences-API erweitern | 4h | UI-Einstellungen (Sidebar collapsed, theme, language, active tab, sort preferences) per API speichern/laden. | -| 5.3 | Workflow-API-Frontend-Modul | 4h | `api/workflows.ts` erstellen. Workflow-Definitions CRUD, Instances, Step-History. | -| 5.4 | Playwright E2E-Tests: Setup | 4h | `@playwright/test` installieren. `playwright.config.ts`. Test-Helper für Login, API-Calls. | -| 5.5 | Playwright: auth.spec.ts | 3h | Login → Logout E2E-Test. | -| 5.6 | Playwright: contact-crud.spec.ts | 4h | Contact erstellen → bearbeiten → Ansprechpartner hinzufügen → löschen. | -| 5.7 | Playwright: search.spec.ts | 3h | Globale Suche, Filter, Ergebnisse prüfen. | -| 5.8 | Playwright: plugin-toggle.spec.ts | 3h | Plugin aktivieren/deaktivieren, UI-Änderung prüfen. | -| 5.9 | Playwright: mail.spec.ts | 4h | Mail-Konto anlegen, Ordner anzeigen, Mail öffnen. | -| 5.10 | Playwright: dms.spec.ts | 4h | Ordner erstellen, Datei hochladen, Vorschau, teilen. | -| 5.11 | Playwright: calendar.spec.ts | 4h | Termin erstellen, Kalender wechseln, Kanban-View. | -| 5.12 | API-Health-Check-Script für KI | 4h | `scripts/ai_health_check.py`: Prüft alle API-Endpunkte, gibt strukturierten Report. KI kann das vor/nach Updates laufen lassen. | -| 5.13 | CI/CD-Pipeline für KI-Updates | 6h | `scripts/ai_deploy.py`: KI kann Build erstellen, Tests laufen, bei Erfolg deployen. Rollback bei Fehler. | -| 5.14 | API-Dokumentation vervollständigen | 4h | OpenAPI/Swagger prüfen. Alle Endpoints dokumentiert. Beispiele für KI. | -| 5.15 | Automatisiertes Backup-System | 8h | `pg_dump` + Storage-Backup als Cron-Job (nutzt Cron-Scheduler aus Phase 3.5). Backup-Konfiguration in Settings (Intervall, Aufbewahrung, Ziel: lokal/S3/Nextcloud). Restore-Script. Backup-Status in Dashboard. Notification bei Backup-Fehler. | -| 5.16 | MCP-Server Integration | 10h | LeoCRM als MCP-Server: Externe Tools (Claude Desktop, andere KI-Clients) können auf LeoCRM-Daten zugreifen. MCP-Tools für Contacts, Calendar, Mail, DMS. Authentifiziert via API-Token. MCP-Config-Endpoint `GET /api/v1/mcp/tools`. | -| 5.17 | MCP-Client Integration | 6h | LeoCRM-Agenten können externe MCP-Server nutzen (z.B. Web-Search, Code-Execution, externe Datenquellen). MCP-Client in tool_registry integriert. Admin kann MCP-Server in Settings konfigurieren. Agenten nutzen MCP-Tools wie native Tools. | -| 5.18 | Report Generator: PDF-Support & Druck-Funktionen | 8h | Backend: WeasyPrint für PDF-Generierung aus Jinja2-Templates. Vorgefertigte Berichte: Kontaktliste, Kalender (Woche/Monat), Firmenliste, Audit-Log. Druck-Optimierte Templates (A4, Landscape). `output_format` um `pdf` und `print` erweitern. | -| 5.19 | Report Generator: Frontend-Oberfläche | 10h | `Reports.tsx` Seite: Template-Liste, Template-Editor (Code-Editor für Jinja2), Report-Generierung mit Live-Preview, Download-History. Vorgefertigte Berichte als Buttons ("Kontakt-Liste drucken", "Kalender drucken"). Druck-Dialog mit Format-Auswahl (A4/A5/Landscape). | -| 5.20 | Custom Fields: Plugin-Felder in UI | 6h | Plugins sollen Custom Fields mitbringen können. Plugin-Manifest um `custom_fields` Definition erweitern. Frontend: Dynamische Custom-Field-Renderer in Contact-Detail, ContactEditModal. Feld-Typen: text, number, date, select, multiselect, boolean. Felder werden in `contacts.custom` JSONB gespeichert. | -| 5.21 | Tasks-Plugin | 12h | Eigenes Tasks-Plugin: Freie Aufgaben/Aktivitäten verwalten (Anruf protokollieren, Notiz, Besuch). Verknüpfung mit Kontakten. Tasks haben Status (open/in_progress/done), Priorität, Fälligkeitsdatum, Zuweisung an Nutzer. Tasks-Liste mit Filter. ARQ-Reminder für fällige Tasks. Plugin-Manifest, Models, Routes, Schemas, Frontend-Seite. | -| 5.22 | Saved Searches / Smart Lists | 6h | Jede Listen-Ansicht (Contacts, Mail, Calendar, DMS) bekommt Filter-Funktionalität. Filter können gespeichert werden (Name, Filter-Kriterien). Gespeicherte Filter erscheinen als Tabs oder Sidebar-Einträge. `saved_filters` Tabelle (tenant-scoped, user-scoped). Frontend: Filter-Builder UI, Save-Button, Load-Gespeicherte-Filter. | -| 5.23 | Deduplication / Merge (über KI/Automatisierung) | 6h | Contacts-Plugin bietet Dubletten-Erkennung: KI-gestützter Vergleich von Kontakten (Name, E-Mail, Telefon). Automation-Template: "Dubletten finden und zusammenführen". Merge-UI: Zwei Kontakte vergleichen, Felder auswählen, zusammenführen. `contact_merge_history` Tabelle. | -| 5.24 | PWA (Progressive Web App) | 6h | Frontend als PWA planen: `manifest.json`, Service Worker, Offline-Caching für statische Assets, Add-to-Home-Screen, App-Icon. Vite PWA Plugin installieren. Push-Notifications vorbereiten (Notification API). | -| 5.25 | Dashboard-System ausbauen | 8h | Plugins bringen Dashboard-Komponenten mit und melden diese an. Plugin-Manifest um `dashboard_widgets` erweitern (bereits in Architektur definiert aber nicht implementiert). Dashboard lädt Widgets dynamisch aus Plugin-Registry. Widget-Typen: Stat-Cards, Charts, Recent-Activity, Quick-Actions. Frontend: Dashboard-Grid mit drag-and-drop Widget-Positionierung. | - -**Phase 5 Gesamt: ~145h** - ---- - -### PHASE 6: React Hook Form + Zod überall -**Ziel:** Konsistente Form-Validierung in allen Formularen - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 6.1 | ComposeModal (Mail) auf RHF + Zod | 4h | E-Mail-Validierung, Pflichtfelder, CC/BCC. | -| 6.2 | AppointmentModal (Calendar) auf RHF + Zod | 4h | Datum-Validierung, Pflichtfelder, Recurrence. | -| 6.3 | SettingsForms auf RHF + Zod | 6h | SettingsUsers, SettingsRoles, SettingsGroups, SettingsCurrencies, SettingsTaxes, SettingsSequences, SettingsSystem. | -| 6.4 | DMS-Forms (Folder create, Share) auf RHF + Zod | 3h | | -| 6.5 | Tag-Forms auf RHF + Zod | 2h | | -| 6.6 | Mail-Settings-Forms auf RHF + Zod | 4h | Account-Erstellung, Rules, Signatures, Templates. | - -**Phase 6 Gesamt: ~23h** - ---- - -### PHASE 7: Test-Vollendung & Wartbarkeit -**Ziel:** Vollständige Test-Abdeckung für KI-Wartbarkeit - -| # | Aufgabe | Aufwand | Details | -|---|---|---|---| -| 7.1 | Tests für ungetestete Settings-Pages | 6h | SettingsGroups, SettingsSystem, SettingsCurrencies, SettingsTaxes, SettingsSequences, SettingsNotifications, SettingsPlugins. | -| 7.2 | Tests für AI-Komponenten | 4h | ChatWindow, SessionList, SuggestionSidebar, AISettings, ProactiveAISettings. | -| 7.3 | Tests für Calendar-Page | 3h | Calendar.tsx (717 Zeilen), CalendarKanban.tsx. | -| 7.4 | Tests für DMS-Sub-Komponenten | 4h | FileExplorer, SourceTree, FileGrid, FileDetails, BulkActions. | -| 7.5 | Tests für Contact-Sub-Komponenten | 3h | ContactDetail, ContactEditModal, ContactFolderTree. | -| 7.6 | Tests für Comm-Blocks | 3h | BlockRenderer und alle Block-Typen. | -| 7.7 | Tests für Stores | 2h | authStore, uiStore, commStore, pluginToolbarStore, calendarStore. | -| 7.8 | Backend-Test-Lücken schließen | 8h | Tests für fehlende Plugin-Routes, Edge-Cases, Multi-Tenant-Szenarien. | -| 7.9 | Test-Runner-Script für KI | 3h | `scripts/ai_run_tests.py`: Führt alle Tests aus (Backend + Frontend + E2E), gibt strukturierten Report. | - -**Phase 7 Gesamt: ~36h** - ---- - -## Zusammenfassung: Aufwandsschätzung (korrigiert) - -| Phase | Thema | Aufwand | Vorher | Änderung | -|---|---|---|---|---| -| 0 | Vorbereitung & Cleanup | ~77h | ~14h | **+63h** (Design, Theme, RBAC, LiteLLM, Search-RBAC, Undo, Storage, Import/Export, Config-Cleanup, Mail-Salt, PyMuPDF→pypdf, OnlyOffice→Collabora) | -| 1 | Unified Contact (Backend+Frontend) | **~81h** | ~33h | **+48h** — Company-Referenzen in 6 Plugins + Permission-Registry + Addresses + conftest unterschätzt | -| 2 | Code-Splitting & Performance | ~25h | ~25h | — | -| 3 | Plugin-UI-System | ~58h | ~48h | +10h (Plugin-Install-System) | -| 3.5 | Automation & Agents Plugin | ~105h | — | **NEU** — Agent Builder, Automation, Cron, Logs, Safety, Agent-zu-Agent, Versionshistorie, MiniApps | -| 4 | KI-UI-Steuerung | ~44h | ~44h | — | -| 5 | API, Testbarkeit, Backup, MCP, Reports, Custom Fields, Tasks, Saved Searches, Dedup, PWA, Dashboard | ~145h | ~57h | +88h | -| 6 | React Hook Form + Zod | ~23h | ~23h | — | -| 7 | Test-Vollendung | ~36h | ~36h | — | -| | **GESAMT** | **~590h** | ~280h | **+310h** | - ---- - -## Empfohlene Reihenfolge - -``` -Phase 0 (Vorbereitung & Cleanup) - ↓ -Phase 1 (Unified Contact — Backend+Frontend) ← Core-CRM-Feature, größte Phase - ↓ -Phase 2 (Code-Splitting & Performance) - ↓ -Phase 3 (Plugin-UI-System) ← WordPress-Style, nicht zu lange schieben - ↓ -Phase 3.5 (Automation & Agents Plugin) ← Agent Builder, Cron-Scheduler, Automation - ↓ -Phase 4 (KI-UI-Steuerung) ← Baut auf Plugin-System auf - ↓ -Phase 5 (API-Vollständigkeit & Testbarkeit) ← KI kann selbstständig testen - ↓ -Phase 6 (React Hook Form + Zod) ← Qualität - ↓ -Phase 7 (Test-Vollendung) ← Wartbarkeit für KI -``` - -**Begründung der Reihenfolge:** -1. Phase 0 zuerst: Dependencies und Cleanup als Fundament -2. Phase 1 als Nächstes: Core-CRM-Feature (Contacts) muss vollständig sein. Größte Phase (~74h) weil 'company' überall im Code verankert ist. -3. Phase 2: Code-Splitting ist schnell und bringt sofortige Performance-Verbesserung -4. Phase 3: Plugin-UI-System — je früher desto besser, sonst wird Umbau später schwieriger -5. Phase 4: KI-UI-Steuerung baut auf Plugin-System auf (dynamische Routes, Tabs etc.). Bestehende WebSocket-Infrastruktur aus kommunikation Plugin als Referenz. -6. Phase 5: API-Vollständigkeit und E2E-Tests für KI-Wartbarkeit -7. Phase 6+7: Qualität und Test-Vollendung - ---- - -## Was bei der Überprüfung gefunden wurde - -### Phase 1 Korrektur: +41h Aufwand - -Die ursprüngliche Schätzung von 33h für Phase 1 war **massiv unterschätzt**. Die gründliche Code-Analyse zeigte: - -**'company' als entity_type ist in 6 Plugins verankert:** -- `entity_links`: entity_type Pattern, company_router, on_company_deleted Event-Handler -- `unified_search`: CompanySearchProvider, index_company, company.created/updated Events, search_engine Mapping -- `calendar`: entity_type Pattern für EntryLinks -- `tags`: entity_type Pattern für Tag-Assignments -- `mail`: company_id Spalte in mails Tabelle (DB-Migration nötig!) -- `ai/action_mapper`: Company-Intents (create/delete/update/list) - -**Event-Namen müssen migriert werden:** -- `company.created` → `contact.created` -- `company.updated` → `contact.updated` -- `company.deleted` → `contact.deleted` -- Betroffen: unified_search, entity_links, workflows, test_sample, manifest.py - -**DB-Migration nötig:** -- `entity_links.entity_type = 'company'` → `'contact'` -- `tag_assignments.entity_type = 'company'` → `'contact'` -- `calendar_entry_links.entity_type = 'company'` → `'contact'` -- `mails.company_id` → `mails.contact_id` (Spalte umbenennen) - -**Was NICHT geändert wird:** -- `system_settings.company_name`, `company_street` etc. → Das ist die CRM-Besitzer-Firmeninfo für Rechnungen. Bleibt wie es ist. -- `CalendarType = 'company'` → Das ist ein Kalender-Typ (Firmenkalender), keine Entity-Referenz. Kann bleiben. - -### Bestehende WebSocket-Infrastruktur -Das `kommunikation` Plugin hat bereits eine vollständige WebSocket-Implementierung (`/api/v1/comm/ws`, `websocket_manager.py`). Diese kann als Referenz für die KI-UI-Steuerung (Phase 4) dienen — das spart Entwicklungszeit. - ---- - -## KI-Wartbarkeit: Schlüssel-Anforderungen - -Damit ein KI-Agent die App selbstständig warten kann: - -1. **Vollständige API-Abdeckung:** Jede UI-Funktion per API steuerbar (Phase 5) -2. **E2E-Tests:** Playwright-Tests die KI ausführen kann (Phase 5) -3. **API-Health-Check:** Script das alle Endpunkte prüft (Phase 5) -4. **Test-Runner:** Script das alle Tests ausführt und strukturiert reportet (Phase 7) -5. **Deploy-Script:** KI kann Build erstellen, testen, deployen, rollback (Phase 5) -6. **Plugin-Richtlinien:** Klare Vorgaben damit KI neue Plugins erstellen kann (Phase 3) -7. **Dokumentation:** Aktuelle Architektur-Doku, API-Doku, Plugin-Guide (Phase 0+3+5) - ---- - -## Nächste Schritte - -1. ✅ Nextcloud Backup erstellt (`/Backups/leocrm/leocrm-backup-20260722.bundle`) -2. ✅ Plan gründlich überprüft und korrigiert (+45h) -3. ⬜ Plan freigeben -4. ⬜ Phase 0 starten -5. ⬜ Planungsdokumente aktualisieren - ---- - -## Test-Strategie (pro Phase) - -### Phase 0: Vorbereitung & Cleanup -- **Pro Task:** Unit-Test für geänderte Funktionalität (z.B. Test dass lucide-react Icons rendern, Test dass date-fns formatiert, Test dass Storage Backend local+S3 funktioniert) -- **Regression:** Alle bestehenden Tests müssen weiterhin durchlaufen -- **Lizenz-Test:** `pip-licenses` Script prüft dass keine AGPL-Packages mehr in requirements.txt - -### Phase 1: Unified Contact Model -- **Pro Task:** API-Integration-Test (httpx + pytest) für jeden geänderten Endpoint -- **DB-Migration-Test:** Test dass Migration 0023 (entity_type company→contact) korrekt ausführt und rollbackbar ist -- **Plugin-Test:** Pro Plugin (entity_links, unified_search, calendar, tags, mail) Test dass entity_type='contact' funktioniert -- **Frontend-Test:** Vitest für ContactDetail, ContactEditModal, ContactPerson-Verwaltung -- **Cross-Tenant-Test:** Test dass Tenant-Isolation nach Migration noch funktioniert - -### Phase 2: Code-Splitting & Performance -- **Bundle-Test:** Test dass Initial-Bundle < 300KB (vorher alle Pages im Bundle) -- **Virtual Scrolling Test:** Test mit 10.000 Datensätzen — Rendering-Zeit < 500ms -- **Lazy-Loading Test:** Test dass Plugin-Pages nicht im Initial-Bundle sind - -### Phase 3: Plugin-UI-System -- **PluginRegistry-Test:** Test dass Manifests korrekt geladen und gerendert werden -- **PluginLoader-Test:** Test dass lazy-loaded Komponenten mit Suspense funktionieren -- **Plugin-Install-Test:** Test dass ZIP-Upload validiert und installiert wird -- **Error-Boundary-Test:** Test dass fehlerhaftes Plugin nicht die ganze App crashen lässt - -### Phase 3.5: Automation & Agents -- **Cron-Scheduler-Test:** Test dass Cron-Jobs zur richtigen Zeit enqueued werden -- **Workflow-Timeout-Test:** Test dass abgelaufene Workflows cancelled werden -- **Agent-Runner-Test:** Test dass Agent LLM-Call ausführt und Ergebnis zurückgibt (Mock-LLM) -- **Automation-Engine-Test:** Test dass Event-Trigger → Conditions → Actions korrekt ausgeführt werden -- **Agent-zu-Agent-Test:** Test dass Agent A Nachricht an Agent B sendet und B reagiert -- **Rate-Limiting-Test:** Test dass Agent nach Max-Ausführungen gestoppt wird -- **Dry-Run-Test:** Test dass Dry-Run keine destruktiven Actions ausführt - -### Phase 4: KI-UI-Steuerung -- **WebSocket-Test:** Test dass Commands korrekt gesendet und empfangen werden -- **Command-Test:** Pro Command-Typ (navigate, filter, open_contact, modal, tab, settings) ein Test -- **Feedback-Test:** Test dass Frontend Bestätigung an KI zurücksendet - -### Phase 5: API-Vollständigkeit & Features -- **E2E-Tests (Playwright):** auth, contact-crud, search, plugin-toggle, mail, dms, calendar (7 Specs) -- **API-Health-Check-Test:** Test dass alle Endpoints erreichbar und korrekt responden -- **Backup-Test:** Test dass Backup erstellt wird und Restore funktioniert -- **MCP-Test:** Test dass MCP-Server Tools bereitstellt und MCP-Client Tools nutzt -- **Report-Test:** Test dass PDF/CSV/Excel generiert wird und korrekt formatiert ist -- **Custom-Fields-Test:** Test dass Plugin-Felder in UI gerendert und gespeichert werden -- **Tasks-Plugin-Test:** Vollständige CRUD-Tests für Tasks -- **Saved-Searches-Test:** Test dass Filter gespeichert und geladen werden -- **Dedup-Test:** Test dass Dubletten erkannt und gemerged werden -- **PWA-Test:** Test dass Service Worker registriert wird und Offline-Caching funktioniert -- **Dashboard-Test:** Test dass Plugin-Widgets dynamisch gerendert werden - -### Phase 6: React Hook Form + Zod -- **Pro Form:** Test dass Validierung korrekt funktioniert (Pflichtfelder, E-Mail-Format, Datum-Range) -- **Error-Display-Test:** Test dass Fehlermeldungen korrekt angezeigt werden - -### Phase 7: Test-Vollendung -- **Coverage-Target:** >80% Backend, >70% Frontend -- **Test-Runner-Script:** `scripts/ai_run_tests.py` führt alle Tests aus und gibt strukturierten Report -- **Multi-Tenant-Test:** Test mit 3 Tenants — Isolation, Cross-Tenant-Access → 404 -- **Performance-Test:** 200k Contacts — List < 500ms, FTS < 500ms - -### Test-Infrastruktur -- **Backend:** pytest + httpx + pytest-asyncio + pytest-cov (bereits vorhanden) -- **Frontend:** Vitest + @testing-library/react (bereits vorhanden) -- **E2E:** Playwright (neu in Phase 5) -- **Test-DB:** PostgreSQL mit `pytest-asyncio` fixture (bereits in conftest.py) -- **Test-Redis:** Redis-Mock oder echte Redis-Instanz -- **Mock-LLM:** LiteLLM mock mode für AI-Tests (bereits vorhanden) - ---- - -## Agent-Anleitung: Wie ein KI-Agent diesen Plan umsetzt - -Dieser Plan ist so strukturiert dass ein KI-Agent (wie Agent Zero) ihn Task-für-Task umsetzen kann. - -### Vorgehensweise pro Task - -1. **Task lesen:** Jeder Task hat Nummer, Aufwand, Beschreibung und Details -2. **Code prüfen:** Vor der Umsetzung den aktuellen Code inspizieren (Dateien lesen, Abhängigkeiten prüfen) -3. **Minimal-invasiv arbeiten:** Nur das ändern was der Task verlangt. Keine Refactoring-Touren. -4. **Tests schreiben/aktualisieren:** Pro Task mindestens ein Test der die Änderung abdeckt -5. **Commit:** Pro Task ein Git-Commit mit klarer Message (z.B. `Phase 0.2: install lucide-react and migrate icons`) -6. **Verifizieren:** Nach jedem Task: Tests laufen, Build funktioniert, keine Regressionen - -### Phasen-Reihenfolge ist verbindlich - -- Phase N+1 darf erst starten wenn Phase N abgeschlossen ist -- Innerhalb einer Phase können Tasks parallel sein (z.B. 0.2 und 0.3 unabhängig) -- Abhängigkeiten sind in den Task-Beschreibungen genannt - -### Was ein Agent pro Task braucht - -- Dateipfade der zu ändernden Dateien (in Task-Beschreibung genannt) -- Akzeptanzkriterien (in Task-Beschreibung genannt) -- Test-Strategie (pro Task mindestens ein Test) -- Git-Commit pro Task - -### Plugin-Entwicklung - -Wenn ein Agent ein neues Plugin erstellt (z.B. Tasks-Plugin 5.21): -1. Plugin-Verzeichnis in `app/plugins/builtins//` erstellen -2. `plugin.py` mit Manifest (Name, Version, Dependencies, Routes, Permissions, Events) -3. `models.py` mit SQLAlchemy Models (TenantMixin!) -4. `schemas.py` mit Pydantic Schemas -5. `routes.py` mit FastAPI Router (require_permission!) -6. `services.py` mit Business-Logic -7. Migration in `migrations/` Verzeichnis -8. Frontend-Komponenten in `frontend/src/components//` -9. Frontend-Seite in `frontend/src/pages/.tsx` -10. API-Modul in `frontend/src/api/.ts` -11. Route in `frontend/src/routes/index.tsx` registrieren -12. i18n-Keys in `frontend/src/i18n/locales/de.json` und `en.json` -13. Tests in `tests/test_.py` und `frontend/src/__tests__//` - -### Plugin-Manifest-Format (für neue Plugins) - -```python -manifest = PluginManifest( - name="my_plugin", - version="1.0.0", - display_name="My Plugin", - description="What it does", - dependencies=["permissions"], # other plugins this depends on - routes=[PluginRouteDef(path="/api/v1/my-plugin", module="...", router_attr="router")], - events=["my.event"], # events this plugin listens to - migrations=["0001_initial.sql"], - permissions=["my_plugin:read", "my_plugin:write"], - is_core=False, - # Neue Felder (nach Phase 3+3.5): - # agent_definitions=[...], # Agent-Templates - # automation_templates=[...], # Automation-Vorlagen - # cron_jobs=[...], # Periodische Tasks - # custom_fields=[...], # Custom Field Definitionen - # dashboard_widgets=[...], # Dashboard-Komponenten - # miniapps=[...], # MiniApp-Definitionen -) -``` - -### Wichtige Regeln für Agent-Updates - -1. **Niemals Tests ändern** um sie grün zu bekommen — Code fixen nicht Tests anpassen -2. **Niemals .env committen** — Secrets gehören nicht ins Repo -3. **Jede DB-Änderung braucht Alembic-Migration** — keine manuellen SQL-Changes -4. **Jede API-Route braucht RBAC** — `require_permission()` auf jedem Endpoint -5. **Jedes Plugin-Model braucht TenantMixin** — tenant_id auf jeder Tabelle -6. **Frontend-Änderungen brauchen i18n** — alle Texte in de.json und en.json -7. **Pro Task ein Commit** — nicht mehrere Tasks in einem Commit -8. **Nach jedem Task: Tests + Build verifizieren** — keine Regressionen -9. **Nach jedem Task: Progress aktualisieren** — `PROGRESS.md` im Repo aktualisieren mit: Task-Nummer, Status (done/in-progress/blocked), Datum, was gemacht wurde, was als Nächstes ansteht. **Zwingend für jeden Agenten der am Plan arbeitet.** diff --git a/PLUGIN-SYSTEM-UMBAUPLAN.md b/PLUGIN-SYSTEM-UMBAUPLAN.md deleted file mode 100644 index e3b054b..0000000 --- a/PLUGIN-SYSTEM-UMBAUPLAN.md +++ /dev/null @@ -1,921 +0,0 @@ -# LeoCRM Plugin-System — Kompletter Umbauplan - -**Erstellt:** 2026-07-26 -**Aktualisiert:** 2026-07-26 (Codebasis-Verifikation + Phase 6) -**Geschätzter Gesamtaufwand:** ~149 Stunden (~19 Arbeitstage) -**Status:** Geplant — noch nicht gestartet - -**Codebasis-Verifikation (2026-07-26):** -- ✅ `base.py` unverändert — Plan passt -- ✅ `registry.py` unverändert — Plan passt -- ✅ `manifest.py` unverändert — Plan passt -- ✅ `contracts.py` (ContractRegistry) unverändert — Plan passt -- ✅ Migration 0044 hinzugekommen: RLS Repair + separater DB-User (crm_runtime) — beeinflusst Plugin-System nicht -- ✅ Migration 0045 hinzugekommen — neuer Head -- ✅ `require_active_plugin` in `deps.py` hinzugekommen — beeinflusst Plugin-System nicht -- ✅ 19 echte Plugins (test_sample hat __init__.py statt plugin.py) -- ✅ Cross-Imports: 224, Contracts: 8, get_contract: 11 — unverändert - ---- - -## Übersicht: 5 Phasen - -| Phase | Punkte | Inhalt | Stunden | Tage | -|---|---|---|---|---| -| Phase 1 | 1-3 | Contracts konsequent nutzen | 47 | 6 | -| Phase 2 | 4 | Hooks/Filters-System | 16 | 2 | -| Phase 3 | 5 | Plugin-Isolation (Linting) | 4 | 0,5 | -| Phase 4 | 8 | Plugin-Versioning | 20 | 2,5 | -| Phase 5 | 6 | Marketplace-Vorbereitung | 42 | 5 | -| Phase 6 | — | Manifest-Anpassung & Konsolidierung | 20 | 2,5 | -| **Gesamt** | | | **149** | **~19** | - -**Wichtig:** Jede Phase ist unabhängig funktionsfähig. Das System läuft nach jeder Phase ohne Einschränkungen weiter. - ---- - -## Phase 1: Contracts konsequent nutzen (Punkte 1-3) - -**Ziel:** Alle 224 direkten Cross-Plugin-Imports werden durch das Contract-System ersetzt. - -### 1.1 Fehlende contracts.py erstellen (7 Std) - -Für jedes Plugin, das noch keine `contracts.py` hat, eine erstellen: - -| # | Plugin | Exportierte Symbole | Aufwand | -|---|---|---|---| -| 1 | `ai_proactive` | ContextTools, ProactiveAgent, JobScheduler | 30 Min | -| 2 | `ai_ui_control` | WebSocketManager, UIAction | 30 Min | -| 3 | `automation` | AgentRunner, ExecutionEngine, Scheduler, WorkflowTimeout | 45 Min | -| 4 | `entity_links` | EntityLink model, create_link, get_links | 20 Min | -| 5 | `forgejo_error_reporter` | report_error_to_forgejo | 15 Min | -| 6 | `mcp_client` | McpClient, McpServerConfig | 30 Min | -| 7 | `mcp_server` | McpServer, ToolDefinitions | 30 Min | -| 8 | `report_generator` | ReportTemplate, ReportInstance, PdfGenerator | 30 Min | -| 9 | `system_notif` | SystemNotifHandler | 15 Min | -| 10 | `tags` | Tag, TagAssignment, assign_tags, remove_tags | 20 Min | -| 11 | `tasks` | Task, TaskService, create_task, update_task | 30 Min | -| 12 | `test_sample` | TestSamplePlugin | 10 Min | -| 13 | `dms` (erweitern) | File, Folder, UploadService, DownloadService | 30 Min | -| 14 | `permissions` (erweitern) | ShareLink, PermissionResolver | 30 Min | - -**Schema für jede contracts.py:** -```python -"""Public contract for the plugin.""" -from __future__ import annotations -from app.plugins.builtins.contracts import get_contract_registry -# Import only public symbols from internal modules - -class Contract: - contract_name = "" - # Expose only public API - -_contract = Contract() -get_contract_registry().register("", _contract) -``` - -### 1.2 Direkte Imports ersetzen (28 Std) - -224 direkte Imports müssen durch `get_contract()` ersetzt werden. - -**Top-Priorität (häufigste Import-Quellen):** - -| # | Datei | Imports | Aufwand | -|---|---|---|---| -| 1 | `automation/plugin.py` | 10 | 1,5 Std | -| 2 | `automation/routes.py` | 8 | 1,5 Std | -| 3 | `ai_proactive/services.py` | 8 | 1,5 Std | -| 4 | `ai_proactive/plugin.py` | 8 | 1,5 Std | -| 5 | `unified_search/jobs.py` | 7 | 1 Std | -| 6 | `builtins/__init__.py` | 7 | 1 Std | -| 7 | `ai_proactive/jobs.py` | 7 | 1 Std | -| 8 | `ai_assistant/participant_handler.py` | 7 | 1 Std | -| 9 | `kommunikation/routes.py` | 6 | 1 Std | -| 10 | `kommunikation/contracts.py` | 6 | 1 Std | -| 11 | `automation/agent_routes.py` | 6 | 1 Std | -| 12 | `automation/agent_comm.py` | 6 | 1 Std | -| 13 | `ai_proactive/participant_handler.py` | 6 | 1 Std | -| 14 | `ai_assistant/plugin.py` | 6 | 1 Std | -| 15 | `unified_search/routes.py` | 5 | 45 Min | -| 16-50 | Alle übrigen Dateien | ~122 | 12 Std | - -**Muster für Ersetzung:** -```python -# VORHER (direkt): -from app.plugins.builtins.kommunikation.services import send_message - -# NACHHER (über Contract): -from app.plugins.builtins.contracts import get_contract - -async def my_function(db, ...): - komm = get_contract("kommunikation") - if komm: - await komm.send_message(db, ...) - # Graceful degradation wenn Plugin nicht aktiv -``` - -### 1.3 Contracts bei Deaktivierung abmelden (4 Std) - -In jedem Plugin's `on_deactivate()`: -```python -async def on_deactivate(self, db, service_container, event_bus) -> None: - # Contract abmelden - from app.plugins.builtins.contracts import get_contract_registry - get_contract_registry().unregister(self.manifest.name) - # ... rest of cleanup - await super().on_deactivate(db, service_container, event_bus) -``` - -| # | Plugin | Aufwand | -|---|---|---| -| 1-16 | Alle 16 Plugins | 15 Min pro Plugin = 4 Std | - -### 1.4 Tests anpassen (8 Std) - -- Cross-Plugin-Tests müssen mit Contracts laufen -- `test_plugins.py` — Contract-Registry Tests -- `test_contracts.py` — Neue Test-Datei für Contract-System -- Alle Integrationstests mit Contract-Mocks - -### Meilenstein Phase 1: -- ✅ Alle 16 Plugins haben contracts.py -- ✅ 0 direkte Cross-Plugin-Imports (geprüft mit grep) -- ✅ Contracts werden bei Deaktivierung abgemeldet -- ✅ Alle Tests bestanden - ---- - -## Phase 2: Hooks/Filters-System (Punkt 4) - -**Ziel:** WordPress-Style Hooks (actions + filters) für Plugin-Erweiterbarkeit. - -### 2.1 HookRegistry erstellen (4 Std) - -**Neue Datei: `app/core/hooks.py`** - -```python -"""WordPress-style hooks: actions (fire-and-forget) and filters (modify data).""" - -from __future__ import annotations -import logging -from collections import defaultdict -from typing import Any, Callable - -logger = logging.getLogger(__name__) - - -class HookRegistry: - """Central registry for actions and filters. - - Actions: do_action('contact.before_create', data) — no return value - Filters: result = apply_filters('contact.format_name', name) — returns modified value - - Priority: lower numbers run first (default=10). - """ - - _instance: HookRegistry | None = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance._actions: dict[str, list[tuple[int, Callable]]] = defaultdict(list) - cls._instance._filters: dict[str, list[tuple[int, Callable]]] = defaultdict(list) - return cls._instance - - def register_action(self, hook_name: str, callback: Callable, priority: int = 10) -> None: - self._actions[hook_name].append((priority, callback)) - self._actions[hook_name].sort(key=lambda x: x[0]) - - def register_filter(self, hook_name: str, callback: Callable, priority: int = 10) -> None: - self._filters[hook_name].append((priority, callback)) - self._filters[hook_name].sort(key=lambda x: x[0]) - - async def do_action(self, hook_name: str, *args, **kwargs) -> None: - for _, callback in self._actions.get(hook_name, []): - try: - result = callback(*args, **kwargs) - if hasattr(result, '__await__'): - await result - except Exception: - logger.exception("Error in action %s", hook_name) - - async def apply_filters(self, hook_name: str, value: Any, *args, **kwargs) -> Any: - for _, callback in self._filters.get(hook_name, []): - try: - result = callback(value, *args, **kwargs) - if hasattr(result, '__await__'): - result = await result - value = result - except Exception: - logger.exception("Error in filter %s", hook_name) - return value - - def unregister(self, hook_name: str, callback: Callable) -> None: - self._actions[hook_name] = [(p, c) for p, c in self._actions.get(hook_name, []) if c != callback] - self._filters[hook_name] = [(p, c) for p, c in self._filters.get(hook_name, []) if c != callback] - - def unregister_all(self, hook_name: str) -> None: - self._actions.pop(hook_name, None) - self._filters.pop(hook_name, None) - - def _reset_for_testing(self) -> None: - self._actions.clear() - self._filters.clear() - - -def get_hook_registry() -> HookRegistry: - return HookRegistry() - -async def do_action(hook_name: str, *args, **kwargs) -> None: - await get_hook_registry().do_action(hook_name, *args, **kwargs) - -async def apply_filters(hook_name: str, value: Any, *args, **kwargs) -> Any: - return await get_hook_registry().apply_filters(hook_name, value, *args, **kwargs) -``` - -### 2.2 Integration in BasePlugin (2 Std) - -```python -# In BasePlugin.on_activate: -async def on_activate(self, db, service_container, event_bus) -> None: - # ... existing code ... - # Hooks werden in Subklassen registriert - -# In BasePlugin.on_deactivate: -async def on_deactivate(self, db, service_container, event_bus) -> None: - # Alle Hooks dieses Plugins abmelden - from app.core.hooks import get_hook_registry - # Plugin-spezifische Hooks entfernen (prefix mit plugin name) - # ... existing code ... -``` - -### 2.3 Hook-Punkte in Core-Services (6 Std) - -| # | Service | Hook-Name | Typ | Beschreibung | -|---|---|---|---|---| -| 1 | contact_service | `contact.before_create` | Action | Vor Kontakt-Erstellung | -| 2 | contact_service | `contact.after_create` | Action | Nach Kontakt-Erstellung | -| 3 | contact_service | `contact.format_display_name` | Filter | Anzeigenamen formatieren | -| 4 | contact_service | `contact.before_update` | Action | Vor Kontakt-Update | -| 5 | contact_service | `contact.after_update` | Action | Nach Kontakt-Update | -| 6 | contact_service | `contact.before_delete` | Action | Vor Kontakt-Löschung | -| 7 | mail_service | `mail.before_send` | Filter | E-Mail vor Versand modifizieren | -| 8 | mail_service | `mail.after_send` | Action | Nach E-Mail-Versand | -| 9 | calendar | `calendar.before_appointment` | Action | Vor Termin-Erstellung | -| 10 | calendar | `calendar.after_appointment` | Action | Nach Termin-Erstellung | -| 11 | auth_service | `auth.before_login` | Filter | Login-Daten validieren/modifizieren | -| 12 | auth_service | `auth.after_login` | Action | Nach erfolgreichem Login | -| 13 | user_service | `user.before_create` | Action | Vor User-Erstellung | -| 14 | user_service | `user.after_create` | Action | Nach User-Erstellung | -| 15 | dms | `dms.before_upload` | Filter | Datei-Upload validieren/modifizieren | - -### 2.4 Tests für Hooks/Filters (4 Std) - -- `test_hooks.py` — HookRegistry Tests -- Integrationstests: Plugin registriert Hook, Core-Service löst Hook aus -- Filter-Tests: Wert wird korrekt modifiziert -- Priority-Tests: Reihenfolge wird eingehalten -- Unregister-Tests: Hooks werden bei Deaktivierung entfernt - -### Meilenstein Phase 2: -- ✅ `app/core/hooks.py` mit HookRegistry -- ✅ 15 Hook-Punkte in Core-Services -- ✅ BasePlugin registriert/unregistriert Hooks automatisch -- ✅ Tests bestanden - ---- - -## Phase 3: Plugin-Isolation (Punkt 5) - -**Ziel:** Direkte Cross-Plugin-Imports werden durch Linting verhindert. - -### 3.1 Linting-Regel erstellen (2 Std) - -**Neue Datei: `.ruff/rules/no_cross_plugin_imports.py`** - -```python -"""Ruff rule: forbid direct imports from app.plugins.builtins.* (except contracts).""" - -# Erlaubt: -# from app.plugins.builtins.contracts import get_contract -# from app.plugins.builtins..contracts import ... -# -# Verboten: -# from app.plugins.builtins..services import ... -# from app.plugins.builtins..models import ... -# from app.plugins.builtins..routes import ... -``` - -### 3.2 CI/CD Integration (1 Std) - -- `ruff check` in GitHub Actions / Forgejo CI -- Pre-commit Hook für lokale Entwicklung -- Fehler bei direkten Cross-Plugin-Imports - -### 3.3 Ausnahmen definieren (1 Std) - -- `conftest.py` — Tests dürfen direkt importieren -- `app/plugins/builtins/__init__.py` — Plugin-Discovery -- `app/plugins/registry.py` — Registry darf importieren - -### Meilenstein Phase 3: -- ✅ Linting-Regel aktiv -- ✅ CI/CD prüft bei jedem Commit -- ✅ 0 direkte Cross-Plugin-Imports (automatisch erzwungen) - ---- - -## Phase 4: Plugin-Versioning (Punkt 8) - -**Ziel:** Vollständige Versionsverwaltung mit SemVer, Rollback und Kompatibilitäts-Check. - -### 4.1 SemVer-Vergleich (3 Std) - -**Neue Datei: `app/plugins/semver.py`** - -```python -"""Semantic version comparison for plugin versions.""" - -from dataclasses import dataclass -import re - -@dataclass -class SemVer: - major: int - minor: int - patch: int - prerelease: str = "" - - @classmethod - def parse(cls, version: str) -> "SemVer": - match = re.match(r"(\d+)\.(\d+)\.(\d+)(?:-(.+))?", version) - if not match: - raise ValueError(f"Invalid semver: {version}") - return cls(int(match[1]), int(match[2]), int(match[3]), match[4] or "") - - def __lt__(self, other): ... - def __eq__(self, other): ... - def __le__(self, other): ... - def __gt__(self, other): ... - - def is_breaking_change(self, other: "SemVer") -> bool: - return self.major != other.major - - def is_compatible_with(self, min_version: "SemVer") -> bool: - return self >= min_version -``` - -**Änderung in `registry.py`:** -```python -# VORHER: String-Vergleich -if record.version != plugin.manifest.version: - -# NACHHER: SemVer-Vergleich -old_ver = SemVer.parse(record.version) -new_ver = SemVer.parse(plugin.manifest.version) -if old_ver != new_ver: - if new_ver < old_ver: - # Downgrade — nur mit Rollback-Migration - ... -``` - -### 4.2 Rollback-Migrationen (6 Std) - -**Erweiterung des Migration-Systems:** - -```python -# MigrationRunner erweitern: -async def run_migration_down(self, db, plugin_name, migration_filename): - """Run rollback (down) migration.""" - # Suche _down.sql oder parse DOWNGRADE-Block - -async def rollback_to_version(self, db, plugin_name, target_version: str): - """Rollback plugin to a specific version.""" - # 1. Finde alle Migrationen nach target_version - # 2. Führe sie in umgekehrter Reihenfolge aus - # 3. Aktualisiere DB-Version -``` - -**Migration-Datei-Format:** -```sql --- 0001_initial.sql --- UP: -CREATE TABLE ...; --- DOWN: -DROP TABLE ... CASCADE; -``` - -Oder separate Dateien: -- `0001_initial_up.sql` -- `0001_initial_down.sql` - -### 4.3 Version-Kompatibilitäts-Check (3 Std) - -**Manifest-Erweiterung:** -```python -class PluginManifest(BaseModel): - # ... existing fields ... - min_app_version: str = Field( - default="0.0.0", - description="Minimum LeoCRM version required" - ) -``` - -**Check bei Installation:** -```python -async def install(self, db, name): - plugin = self.get_plugin(name) - # Check app version compatibility - app_version = SemVer.parse(settings.app_version) - min_version = SemVer.parse(plugin.manifest.min_app_version) - if app_version < min_version: - raise ValueError( - f"Plugin '{name}' requires LeoCRM >= {plugin.manifest.min_app_version}, " - f"but current version is {settings.app_version}" - ) -``` - -### 4.4 Update-Benachrichtigung im Frontend (4 Std) - -**Backend:** -- `GET /api/v1/plugins/updates` — Liste Plugins mit verfügbarer neuer Version -- Vergleich mit Marketplace-Registry (wenn verfügbar) oder lokaler Version - -**Frontend:** -- Badge im Plugin-Settings: "Update verfügbar (1.2.0 → 1.3.0)" -- Update-Button: Löst Update aus (führt neue Migrationen aus) -- Changelog-Anzeige (optional) - -### 4.5 Tests (4 Std) - -- `test_semver.py` — SemVer-Vergleich, Parse, Edge Cases -- `test_versioning.py` — Upgrade, Downgrade, Kompatibilitäts-Check -- `test_rollback.py` — Rollback-Migrationen -- Integrationstests: Version-Update löst Migrationen aus - -### Meilenstein Phase 4: -- ✅ SemVer-Vergleich statt String-Vergleich -- ✅ Rollback-Migrationen funktionieren -- ✅ min_app_version wird geprüft -- ✅ Frontend zeigt Update-Benachrichtigungen -- ✅ Tests bestanden - ---- - -## Phase 5: Marketplace-Vorbereitung (Punkt 6) - -**Ziel:** Code so vorbereiten, dass ein Marketplace nur noch gebaut werden muss — ohne Systemänderungen. - -**Wichtig:** Funktioniert auch OHNE Marketplace — Built-in Plugins laufen normal weiter. - -### 5.1 Externe Plugin-Discovery (6 Std) - -**Erweiterung `registry.py`:** - -```python -class PluginRegistry: - - def discover_all(self) -> list[str]: - """Discover built-in AND external plugins.""" - discovered = self.discover_builtins() - discovered.extend(self.discover_external()) - return discovered - - def discover_external(self) -> list[str]: - """Discover plugins from external plugins/ directory.""" - external_dir = Path(settings.external_plugins_path or "plugins") - if not external_dir.exists(): - return [] - - discovered = [] - for plugin_dir in external_dir.iterdir(): - if not plugin_dir.is_dir() or plugin_dir.name.startswith("_"): - continue - # Look for plugin.py or __init__.py with BasePlugin subclass - plugin_file = plugin_dir / "plugin.py" - if not plugin_file.exists(): - continue - # Import and register - import sys - sys.path.insert(0, str(external_dir)) - try: - module = importlib.import_module(f"{plugin_dir.name}.plugin") - # ... find BasePlugin subclass ... - finally: - sys.path.remove(str(external_dir)) - return discovered -``` - -### 5.2 Plugin-Signatur-Validierung (8 Std) - -**Neue Datei: `app/plugins/signature.py`** - -```python -"""Plugin signature verification for external plugins.""" - -from pathlib import Path -import hashlib -import hmac - -# Ed25519 oder HMAC-SHA256 Signatur - -class PluginSignature: - """Verify plugin package signatures.""" - - @staticmethod - def verify_signature(zip_path: Path, signature: bytes, public_key: bytes) -> bool: - """Verify Ed25519 signature of plugin ZIP.""" - # 1. Read ZIP content - # 2. Compute hash - # 3. Verify signature with public key - pass - - @staticmethod - def compute_hash(zip_path: Path) -> bytes: - """Compute SHA-256 hash of plugin ZIP.""" - pass - - @staticmethod - def sign_plugin(zip_path: Path, private_key: bytes) -> bytes: - """Sign a plugin ZIP (for plugin authors).""" - pass -``` - -### 5.3 Plugin-Allowlist (4 Std) - -**Neue Alembic-Migration: `0044_plugin_allowlist.py`** - -```python -# Tabelle: plugin_allowlist -# - id: UUID -# - plugin_name: VARCHAR(80) -# - allowed_hash: VARCHAR(64) # SHA-256 -# - allowed_signature: TEXT # Ed25519 signature -# - added_by: UUID (user) -# - created_at: TIMESTAMPTZ -# - is_active: BOOLEAN -``` - -### 5.4 Plugin-Metadata-Erweiterung (4 Std) - -**Manifest-Erweiterung:** -```python -class PluginManifest(BaseModel): - # ... existing fields ... - author: str = Field(default="", description="Plugin author") - author_email: str = Field(default="", description="Author contact") - homepage: str = Field(default="", description="Plugin homepage URL") - license: str = Field(default="MIT", description="License") - min_app_version: str = Field(default="0.0.0") - icon: str = Field(default="", description="Icon URL or emoji") - screenshots: list[str] = Field(default_factory=list) - changelog: str = Field(default="", description="Changelog URL or text") - tags: list[str] = Field(default_factory=list, description="Marketplace categories") - price: float = Field(default=0.0, description="Price (0 = free)") -``` - -### 5.5 Plugin-Download-Endpoint (4 Std) - -**Neue Route: `POST /api/v1/plugins/install-marketplace`** - -```python -@router.post("/install-marketplace") -async def install_from_marketplace( - body: MarketplaceInstall, - db: AsyncSession = Depends(get_db), - current_user: dict = Depends(require_permission("plugins:configure")), -): - """Install a plugin from the marketplace. - - 1. Download ZIP from marketplace URL - 2. Verify signature against allowlist - 3. Validate manifest - 4. Check dangerous imports - 5. Validate migration SQL - 6. Install (migrations + DB record) - 7. Activate (optional) - """ - # 1. Download - async with httpx.AsyncClient() as client: - resp = await client.get(body.url) - zip_data = resp.content - - # 2. Verify signature - if not PluginSignature.verify_signature(zip_data, body.signature, public_key): - raise HTTPException(403, "Invalid plugin signature") - - # 3-6. Validate and install - # ... (reuse existing validation + install logic) -``` - -### 5.6 Plugin-Update-Check (4 Std) - -```python -@router.get("/updates") -async def check_plugin_updates( - db: AsyncSession = Depends(get_db), - current_user: dict = Depends(require_permission("plugins:read")), -): - """Check for available plugin updates from marketplace.""" - # 1. Query marketplace registry (if configured) - # 2. Compare versions with installed plugins - # 3. Return list of available updates -``` - -### 5.7 Plugin-Quarantine (4 Std) - -```python -async def _quarantine_plugin(zip_path: Path) -> Path: - """Extract plugin to temp dir, validate, then move to plugins/ dir. - - 1. Extract to /tmp/plugin_upload_/ - 2. Validate manifest exists - 3. Check dangerous imports - 4. Validate migration SQL - 5. Check signature - 6. If all OK: move to plugins/ dir - 7. If any fail: delete temp dir, raise error - """ -``` - -### 5.8 Tests (8 Std) - -- `test_marketplace.py` — Download, Verify, Install Flow -- `test_signature.py` — Signatur-Validierung -- `test_allowlist.py` — Allowlist-Management -- `test_quarantine.py` — Quarantine-Validierung -- `test_external_discovery.py` — Externe Plugin-Discovery -- Integrationstests: Vollständiger Marketplace-Flow - -### Meilenstein Phase 5: -- ✅ Externe Plugins können entdeckt werden -- ✅ Signatur-Validierung funktioniert -- ✅ Allowlist schützt vor nicht autorisierten Plugins -- ✅ Marketplace-Endpoint ist vorbereitet (deaktiviert bis Marketplace live) -- ✅ Plugin-Upload bleibt deaktiviert -- ✅ Built-in Plugins laufen ohne Marketplace -- ✅ Tests bestanden - ---- - -## Phase 6: Manifest-Anpassung & Konsolidierung - -**Ziel:** Alle in Phase 4 und 5 definierten Manifest-Felder werden ins `PluginManifest` integriert, bestehende Manifeste aktualisiert, und das Manifest-System finalisiert. - -**Wichtig:** Diese Phase baut auf Phase 4 (Versioning) und Phase 5 (Marketplace) auf und muss als letztes durchgeführt werden. - -### 6.1 PluginManifest erweitern (4 Std) - -**Aktuelles Manifest (verifiziert 2026-07-26):** -```python -class PluginManifest(BaseModel): - name: str - version: str - display_name: str - description: str - dependencies: list[str] - routes: list[PluginRouteDef] - events: list[str] - migrations: list[str] - permissions: list[str] - is_core: bool - field_definitions: list[FieldDefinition] - agent_capabilities: list[str] - menu_items: list[FrontendMenuItem] - page_routes: list[FrontendPageRoute] - detail_tabs: list[FrontendDetailTab] - settings_pages: list[FrontendSettingsPage] - dashboard_widgets: list[FrontendDashboardWidget] - agent_definitions: list[AgentDefinitionContribution] - automation_templates: list[AutomationTemplateContribution] - cron_jobs: list[CronJobContribution] - heartbeat_configs: list[HeartbeatConfigContribution] - miniapps: list[MiniAppContribution] - custom_fields: list[CustomFieldDefinition] - model_config = {"extra": "forbid"} -``` - -**Neue Felder hinzufügen:** -```python -class PluginManifest(BaseModel): - # ... alle bestehenden Felder ... - - # ── Versioning (Phase 4) ── - min_app_version: str = Field( - default="0.0.0", - description="Minimum LeoCRM version required (SemVer)" - ) - - # ── Marketplace (Phase 5) ── - author: str = Field(default="", max_length=200, description="Plugin author name") - author_email: str = Field(default="", max_length=200, description="Author contact email") - homepage: str = Field(default="", max_length=500, description="Plugin homepage URL") - license: str = Field(default="MIT", max_length=50, description="License identifier") - icon: str = Field(default="", description="Icon URL or emoji") - screenshots: list[str] = Field(default_factory=list, description="Screenshot URLs for marketplace") - changelog: str = Field(default="", description="Changelog URL or inline text") - marketplace_tags: list[str] = Field(default_factory=list, description="Marketplace category tags") - price: float = Field(default=0.0, ge=0.0, description="Price (0 = free)") - - # ── Hooks (Phase 2) ── - hooks: list[str] = Field( - default_factory=list, - description="Hook names this plugin registers (e.g. 'contact.before_create')" - ) - - # ── Contracts (Phase 1) ── - contract_version: str = Field( - default="1.0.0", - description="Contract API version this plugin exposes" - ) -``` - -### 6.2 Manifest-Schema-Dokumentation aktualisieren (3 Std) - -**`MANIFEST_SCHEMA_DOC` in `manifest.py` erweitern:** -- Alle neuen Felder in `fields`-Dict aufnehmen -- `example`-Manifest mit neuen Feldern aktualisieren -- API-Endpoint `GET /api/v1/plugins/manifest` liefert vollständiges Schema - -### 6.3 Alle 19 Plugin-Manifeste aktualisieren (8 Std) - -Jedes Plugin-Manifest muss um die neuen Felder erweitert werden: - -| # | Plugin | Aufwand | Neue Felder | -|---|---|---|---| -| 1 | `ai_assistant` | 30 Min | author, min_app_version, hooks, contract_version | -| 2 | `ai_proactive` | 30 Min | author, min_app_version, hooks, contract_version | -| 3 | `ai_ui_control` | 20 Min | author, min_app_version, contract_version | -| 4 | `automation` | 30 Min | author, min_app_version, hooks, contract_version | -| 5 | `calendar` | 20 Min | author, min_app_version, hooks, contract_version | -| 6 | `dms` | 20 Min | author, min_app_version, hooks, contract_version | -| 7 | `entity_links` | 15 Min | author, min_app_version, contract_version | -| 8 | `forgejo_error_reporter` | 15 Min | author, min_app_version, contract_version | -| 9 | `kommunikation` | 30 Min | author, min_app_version, hooks, contract_version | -| 10 | `mail` | 20 Min | author, min_app_version, hooks, contract_version | -| 11 | `mcp_client` | 20 Min | author, min_app_version, contract_version | -| 12 | `mcp_server` | 20 Min | author, min_app_version, contract_version | -| 13 | `permissions` | 20 Min | author, min_app_version, contract_version | -| 14 | `report_generator` | 20 Min | author, min_app_version, contract_version | -| 15 | `system_notif` | 15 Min | author, min_app_version, contract_version | -| 16 | `tags` | 15 Min | author, min_app_version, contract_version | -| 17 | `tasks` | 20 Min | author, min_app_version, hooks, contract_version | -| 18 | `test_sample` | 10 Min | author, min_app_version, contract_version | -| 19 | `unified_search` | 20 Min | author, min_app_version, hooks, contract_version | - -**Muster für Aktualisierung:** -```python -# VORHER: -manifest = PluginManifest( - name="calendar", - version="1.0.0", - display_name="Calendar", - ... -) - -# NACHHER: -manifest = PluginManifest( - name="calendar", - version="1.0.0", - display_name="Calendar", - # ... bestehende Felder ... - # ── Neue Felder ── - min_app_version="1.0.0", - author="LeoCRM Team", - license="MIT", - hooks=["calendar.before_appointment", "calendar.after_appointment"], - contract_version="1.0.0", -) -``` - -### 6.4 Frontend Plugin-Manifest-Typen aktualisieren (2 Std) - -**`frontend/src/api/pluginManifests.ts` und `frontend/src/types/automation.ts`:** -- TypeScript-Interfaces um neue Manifest-Felder erweitern -- `PluginManifestResponse`-Typ aktualisieren -- Frontend-Komponenten die Manifest-Felder anzeigen erweitern - -### 6.5 Manifest-Validierung verschärfen (3 Std) - -**Neue Validierungsregeln in `PluginManifest`:** -```python -@field_validator("min_app_version") -@classmethod -def validate_min_app_version(cls, v: str) -> str: - """Validate SemVer format.""" - from app.plugins.semver import SemVer - SemVer.parse(v) # Raises ValueError if invalid - return v - -@field_validator("hooks") -@classmethod -def validate_hooks(cls, v: list[str]) -> list[str]: - """Validate hook names follow namespace.pattern.""" - for hook in v: - if not re.match(r"^[a-z_]+\.[a-z_]+$", hook): - raise ValueError(f"Invalid hook name '{hook}': must be 'namespace.action'") - return v -``` - -### 6.6 Tests für erweitertes Manifest (3 Std) - -- `test_manifest.py` — Neue Felder validieren -- `test_manifest_validation.py` — SemVer-Validierung, Hook-Name-Validierung -- Alle Plugin-Tests: Manifest mit neuen Feldern erstellen -- Frontend-Tests: Manifest mit neuen Feldern rendern - -### Meilenstein Phase 6: -- ✅ `PluginManifest` hat alle neuen Felder (min_app_version, author, hooks, contract_version, etc.) -- ✅ `MANIFEST_SCHEMA_DOC` ist vollständig aktualisiert -- ✅ Alle 19 Plugin-Manifeste haben die neuen Felder -- ✅ Frontend-Typen sind aktualisiert -- ✅ Manifest-Validierung ist verschärft -- ✅ Tests bestanden - ---- - -## Zeitplan - -``` -Woche 1 (Tag 1-5): Phase 1 — Contracts (Teil 1: contracts.py + Imports) -Woche 2 (Tag 6-8): Phase 1 — Contracts (Teil 2: Deaktivierung + Tests) - (Tag 9-10): Phase 2 — Hooks/Filters-System -Woche 3 (Tag 11): Phase 3 — Plugin-Isolation - (Tag 12-14): Phase 4 — Plugin-Versioning -Woche 4 (Tag 15-19): Phase 5 — Marketplace-Vorbereitung -Woche 5 (Tag 20-22): Phase 6 — Manifest-Anpassung & Konsolidierung - (Tag 23): Puffer / Bugfixes / Doku -``` - -### Abhängigkeiten -``` -Phase 1 (Contracts) ──→ Phase 3 (Isolation: Linting braucht Contracts als Ausnahme) - │ - └──→ Phase 2 (Hooks: unabhängig, kann parallel) - │ - └──→ Phase 4 (Versioning: braucht Contracts für min_app_version) - │ - └──→ Phase 5 (Marketplace: braucht alles) - │ - └──→ Phase 6 (Manifest: braucht Phase 4 + 5 Felder) -``` - -### Parallelisierungsmöglichkeiten -- Phase 1 und Phase 2 können **parallel** laufen (verschiedene Entwickler) -- Phase 3 kann erst nach Phase 1 starten -- Phase 4 kann nach Phase 1 starten -- Phase 5 kann erst nach Phase 1+4 starten -- Phase 6 kann erst nach Phase 4+5 starten (braucht deren Manifest-Felder) - ---- - -## Risiken - -| Risiko | Wahrscheinlichkeit | Auswirkung | Mitigation | -|---|---|---|---| -| Contract-Refactoring bricht bestehende Funktionalität | Mittel | Hoch | Tests nach jedem Plugin, schrittweise Migration | -| Hooks/Filters verändern Core-Verhalten | Niedrig | Mittel | Tests für alle Hook-Punkte, Priority-System | -| Externe Plugin-Discovery hat Sicherheitslücken | Mittel | Hoch | Signatur-Validierung, Quarantine, Allowlist | -| SemVer-Parse-Fehler bei bestehenden Versionen | Niedrig | Niedrig | Fallback auf String-Vergleich | -| Rollback-Migrationen löschen Daten | Mittel | Hoch | Bestätigungs-Prompt, Backup vor Rollback | - ---- - -## Erfolgskriterien - -Nach Abschluss aller 5 Phasen: - -1. ✅ **0 direkte Cross-Plugin-Imports** (grep-verifiziert, linting-enforced) -2. ✅ **Alle 16 Plugins haben contracts.py** mit klarer öffentlicher API -3. ✅ **Contracts werden bei Deaktivierung abgemeldet** -4. ✅ **Hooks/Filters-System** mit 15+ Hook-Punkten in Core-Services -5. ✅ **Plugin-Isolation** durch Linting-Regeln erzwungen -6. ✅ **SemVer-Vergleich** statt String-Vergleich -7. ✅ **Rollback-Migrationen** für alle Plugins verfügbar -8. ✅ **min_app_version** wird bei Installation geprüft -9. ✅ **Update-Benachrichtigung** im Frontend -10. ✅ **Marketplace-Endpoint** vorbereitet (deaktiviert) -11. ✅ **Signatur-Validierung** für externe Plugins -12. ✅ **Allowlist** schützt vor nicht autorisierten Plugins -13. ✅ **Externe Plugin-Discovery** funktioniert -14. ✅ **Alle Tests bestanden** -15. ✅ **Built-in Plugins laufen ohne Marketplace** -16. ✅ **PluginManifest hat alle neuen Felder** (min_app_version, author, hooks, contract_version, etc.) -17. ✅ **Alle 19 Plugin-Manifeste aktualisiert** mit neuen Feldern -18. ✅ **Manifest-Validierung verschärft** (SemVer, Hook-Names) -19. ✅ **Frontend-Typen aktualisiert** für neue Manifest-Felder - ---- - -## Dokumentation - -Nach Abschluss jeder Phase: -- `docs/plugin-system/phase-N.md` — Was wurde gemacht, was geändert -- `docs/plugin-system/contracts-api.md` — Contract-API Referenz -- `docs/plugin-system/hooks-api.md` — Hooks/Filters Referenz -- `docs/plugin-system/marketplace-api.md` — Marketplace-API Referenz -- `docs/plugin-system/plugin-development-guide.md` — Wie man ein Plugin entwickelt - ---- - -**Dieser Plan ist vollständig. Alle Aufgaben, Aufwände, Abhängigkeiten und Risiken sind erfasst.** diff --git a/PROGRESS.md b/PROGRESS.md deleted file mode 100644 index 5df8e1e..0000000 --- a/PROGRESS.md +++ /dev/null @@ -1,804 +0,0 @@ -# LeoCRM — Umbau Progress - -**Plan:** `MASTER-PLAN.md` -**Start:** 2026-07-23 - ---- - -## Phase 0: Vorbereitung & Cleanup - -| Task | Status | Datum | Notiz | -|---|---|---|---| -| 0.1 | ✅ done | 2026-07-23 | codebase-vs-requirements.md neu geschrieben, security-review-phase2.md Resolution Summary, architecture.md Implementation Status, MASTER-PLAN.md + PROGRESS.md erstellt | -| 0.2 | ✅ done | 2026-07-23 | lucide-react installieren + Icons migrieren | -| 0.3 | ✅ done | 2026-07-23 | date-fns installieren + Datum-Formatierung | -| 0.4 | ✅ done | 2026-07-23 | hooks.ts aufteilen — 1298 Zeilen → 12 Module + Re-Export-Hub | -| 0.5 | ✅ done | 2026-07-23 | calendarStore.ts nach store/ verschoben, stores/ entfernt | -| 0.6 | ✅ done | 2026-07-23 | frontend-gap-analysis.md gespeichert (176 Zeilen) | -| 0.7 | ✅ done | 2026-07-23 | UI-Design-Richtlinien erstellt (docs/ui-design-guidelines.md, 535 Zeilen) | -| 0.8 | ✅ done | 2026-07-23 | Theme-Customization Backend: 4 Felder (primary_color, accent_color, font_family, border_radius) zu model/schema/service, Migration 0023 | Theme-Customization Backend | -| 0.9 | ✅ done | 2026-07-23 | Theme-Customization Frontend: SettingsTheme.tsx, themeStore.ts, Route + Nav, i18n keys, Live-Preview, Dark-Mode-Toggle | Theme-Customization Frontend | -| 0.10 | ✅ done | 2026-07-23 | RBAC-Audit: 4 Plugins (calendar, dms, entity_links, tags) mit Permissions versehen, 53 Routes mit require_permission abgesichert | RBAC-Audit & Plugin-Permissions nachrüsten | -| 0.11 | ✅ done | 2026-07-23 | LiteLLM-Cleanup: llm_client.py von httpx auf litellm.acompletion migriert, AI_PROVIDER env var, System-Prompt companies→contacts | LiteLLM-Cleanup & alte llm_client.py migrieren | -| 0.12 | ✅ done | 2026-07-23 | KI-Agent-Framework: docs/plugin-development-guide.md (348 Zeilen), agent_capabilities Feld im PluginManifest | KI-Agent-Framework in Plugin-Richtlinien dokumentieren | -| 0.13 | ✅ done | 2026-07-23 | Heartbeat konfigurierbar: ProactiveSettings um heartbeat_enabled/interval/target_room erweitert, Migration 0024, Schema+Service+Routes, Frontend-UI, Jobs.py nutzt Settings | Heartbeat konfigurierbar machen | -| 0.14 | ✅ done | 2026-07-23 | Unified Search Field-Level RBAC: resolve_permissions + filter_fields_by_permission in search route, entity-to-module mapping | Unified Search: Field-Level RBAC nachrüsten | -| 0.15 | ✅ done | 2026-07-23 | Undo/History-System: EntityHistory model+service+routes, Migration 0025, contact_service Integration, HistoryViewer Komponente, ContactDetail Integration, i18n | Undo/History-System für CRUD-Operationen | -| 0.16 | ✅ done | 2026-07-23 | Storage Backend: app/core/storage.py (LocalStorage + S3Storage), DMS + Attachments + Mail auf Storage Backend umgestellt, minio zu requirements, S3 env vars | Storage Backend implementieren (S3-Support) | -| 0.17 | ✅ done | 2026-07-23 | Import/Export: unified Contact Fields (firstname, surname, email_1, phone_1, mobilephone, function), Company-Import als Contact type=company, Export mit unified Fields, Backward-compat für alte CSV-Spalten | Import/Export an unified Contact Model anpassen | -| 0.18 | ✅ done | 2026-07-23 | .gitignore: webui→frontend, python-jose entfernt, pyproject.toml Python 3.12, .env aus Git entfernt, dump.rdb+test.txt gelöscht, JWT-Vars aus .env.docker.example entfernt | .gitignore & Config-Cleanup | -| 0.19 | ✅ done | 2026-07-23 | Mail-Salt Security-Fix: per-account random salt (generate_salt), encrypt/decrypt mit salt_b64, backward-compat mit Legacy-Salt, Migration 0026 | Mail-Salt Security-Fix | -| 0.20 | ✅ done | 2026-07-23 | AGPL ersetzt: PyMuPDF→pypdf (BSD), OnlyOffice→Collabora (LGPL/MPL), requirements.txt, LICENSE (MIT), THIRD_PARTY_LICENSES.md | AGPL-Lizenzen durch pypdf + Collabora ersetzen | - ---- - -## Phase 1B: Backend Plugins — entity_type='company' → 'contact' - -| Task | Status | Datum | Notiz | -|---|---|---|---| -| 1.9 | ✅ done | 2026-07-23 | entity_links Plugin: entity_type pattern ^(company|contact)$ → ^contact$, company_router entfernt, on_company_deleted → on_contact_deleted, company.deleted → contact.deleted | -| 1.10 | ✅ done | 2026-07-23 | unified_search Plugin: CompanySearchProvider → ContactSearchProvider, index_company → index_contact, events company.created/updated → contact.created/updated, search_engine mapping aktualisiert | -| 1.11 | ✅ done | 2026-07-23 | calendar Plugin: entity_type pattern ^(company|contact)$ → ^contact$ (CalendarType='company' bleibt) | -| 1.12 | ✅ done | 2026-07-23 | tags Plugin: entity_type pattern ^(company|contact|file|folder)$ → ^(contact|file|folder)$ | -| 1.13 | ✅ done | 2026-07-23 | mail Plugin: company_id → contact_id in model, schemas, routes, services | -| 1.14 | ✅ done | 2026-07-23 | test_sample Plugin: company.created → contact.created | -| 1.15 | ✅ done | 2026-07-23 | Event Names Unify: Alle company.created/updated/deleted → contact.created/updated/deleted | -| 1.16 | ✅ done | 2026-07-23 | DB Migration 0027: entity_type 'company' → 'contact' in entity_links, tag_assignments, calendar_entry_links, addresses; mails company_id → contact_id | -| 1.17 | ✅ done | 2026-07-23 | Backend Tests Update: test_companies.py, test_unified_search.py, test_entity_links.py, test_calendar.py, test_tags.py, test_ai_proactive.py, test_tenant.py — entity_type='company' → 'contact' | -| 1.18 | ✅ done | 2026-07-23 | Permission-Registry-Cleanup: companies:read/write/delete aus CORE_PERMISSIONS entfernt (bereits in 1A) | -| 1.19 | ✅ done | 2026-07-23 | Addresses entity_type='company' → 'contact' in address_service.py (bereits in 1A) | -| 1.20 | ✅ done | 2026-07-23 | conftest.py Update: Company → Contact, CompanyContact → ContactPerson, TRUNCATE ohne companies/company_contacts | - -**Phase 1B Gesamt: ✅ Complete** - ---- - -## Phase 1C: Frontend — Unified Contact UI - -| Task | Status | Datum | Notiz | -|---|---|---|---| -| 1.18 | ✅ done | 2026-07-23 | Contact-Detail-Route /contacts/:id mit React.lazy | -| 1.19 | ✅ done | 2026-07-23 | ContactList Type-Filter Toggle (Alle/Firmen/Personen) | -| 1.20 | ✅ done | 2026-07-23 | ContactDetail ContactPerson-Verwaltung (bereits vorhanden) | -| 1.21 | ✅ done | 2026-07-23 | ContactEditModal für beide Types (bereits vorhanden) | -| 1.22 | ✅ done | 2026-07-23 | Company-Hooks aus hooks.ts entfernt | -| 1.23 | ✅ done | 2026-07-23 | Frontend Type-Definitions aktualisiert (calendar, tags, search, mail, types) | -| 1.24 | ✅ done | 2026-07-23 | Dashboard.tsx aktualisiert (keine Änderung nötig) | -| 1.25 | ✅ done | 2026-07-23 | GlobalSearchResults.tsx aktualisiert | -| 1.26 | ✅ done | 2026-07-23 | ContactFolderTree in ContactList integriert (bereits vorhanden) | -| 1.27 | ✅ done | 2026-07-23 | React Hook Form + Zod in ContactEditModal | -| 1.28 | ✅ done | 2026-07-23 | Frontend-Tests aktualisiert, tsc --noEmit OK (nur pre-existing Dms-Fehler) | - -**Phase 1C Gesamt: ✅ Complete** - ---- - -## Phase 1-7: Noch nicht gestartet - -Siehe `MASTER-PLAN.md` für alle Tasks. - -## Phase 2: Code-Splitting & Virtual Scrolling - -| Task | Status | Datum | Notiz | -|---|---|---|---| -| 2.1 | ✅ done | 2026-07-23 | React.lazy + Suspense für 25 Pages, PageLoader Komponente | -| 2.2 | ✅ done | 2026-07-23 | @tanstack/react-virtual installiert | -| 2.3 | ✅ done | 2026-07-23 | DataGrid Virtual Scrolling (useVirtualizer, 53px rows, 10 overscan, auto-skip <50) | -| 2.4 | ✅ done | 2026-07-23 | MailList Virtual Scrolling (80px rows, 8 overscan, auto-skip <50) | -| 2.5 | ✅ done | 2026-07-23 | ContactList Virtual Scrolling (list/table/cards, dynamic row estimate) | -| 2.6 | ✅ done | 2026-07-23 | FileExplorer + FileGrid virtualisiert, AuditLog via DataGrid | -| 2.7 | ✅ done | 2026-07-23 | Manual chunks: react-vendor, tanstack, ui, i18n. Vite build OK (3212 modules, ~9s) | - -**Phase 2 Gesamt: ✅ Complete (Commit a8331fb)** - -## Phase 1: Unified Contact Model — Vollendung (Backend + Frontend) - -| Task | Status | Datum | Notiz | -|---|---|---|---| -| 1.1 | ✅ done | 2026-07-23 | app/routes/companies.py entfernt | -| 1.2 | ✅ done | 2026-07-23 | app/services/company_service.py entfernt | -| 1.3 | ✅ done | 2026-07-23 | app/models/company.py entfernt (war shim) | -| 1.4 | ✅ done | 2026-07-23 | app/schemas/company.py entfernt | -| 1.5 | ✅ done | 2026-07-23 | action_mapper.py: Company-Intents → Contact-Intents | -| 1.6 | ✅ done | 2026-07-23 | workflows/engine.py: company.created → contact.created | -| 1.7 | ✅ done | 2026-07-23 | worker.py: index_company entfernt | -| 1.8 | ✅ done | 2026-07-23 | seeds.py: keine Company-Seed-Daten gefunden | -| 1.9 | ✅ done | 2026-07-23 | entity_links: entity_type → ^contact$, company.deleted → contact.deleted | -| 1.10 | ✅ done | 2026-07-23 | unified_search: CompanySearchProvider → Contact, index_company → index_contact, events unified | -| 1.11 | ✅ done | 2026-07-23 | calendar: entity_type → ^contact$, CalendarType='company' beibehalten | -| 1.12 | ✅ done | 2026-07-23 | tags: entity_type → ^(contact|file|folder)$ | -| 1.13 | ✅ done | 2026-07-23 | mail: company_id → contact_id in model/schemas/routes/services | -| 1.14 | ✅ done | 2026-07-23 | test_sample: company.created → contact.created | -| 1.15 | ✅ done | 2026-07-23 | Alle company.* events → contact.* events vereinheitlicht | -| 1.16 | ✅ done | 2026-07-23 | DB-Migration 0027: entity_type company→contact, mails.company_id→contact_id | -| 1.17 | ✅ done | 2026-07-23 | Backend-Tests aktualisiert (test_companies, test_unified_search, test_entity_links, etc.) | -| 1.18 | ✅ done | 2026-07-23 | Permission-Registry: companies:read/write/delete entfernt, CORE_FIELD_DEFINITIONS aktualisiert | -| 1.19 | ✅ done | 2026-07-23 | Addresses: entity_type → ^contact$ only | -| 1.20 | ✅ done | 2026-07-23 | conftest.py: Company→Contact, CompanyContact→ContactPerson, TRUNCATE bereinigt | -| 1.21 | ✅ done | 2026-07-23 | Contact-Detail-Route /contacts/:id mit React.lazy | -| 1.22 | ✅ done | 2026-07-23 | Company-Hooks aus hooks.ts entfernt | -| 1.23 | ✅ done | 2026-07-23 | Frontend Type-Definitions aktualisiert (calendar, tags, search, mail, types) | -| 1.24 | ✅ done | 2026-07-23 | Dashboard.tsx aktualisiert (keine Änderung nötig) | -| 1.25 | ✅ done | 2026-07-23 | GlobalSearchResults.tsx: type 'company' → 'contact' | -| 1.26 | ✅ done | 2026-07-23 | ContactFolderTree in ContactList (bereits vorhanden) | -| 1.27 | ✅ done | 2026-07-23 | React Hook Form + Zod in ContactEditModal | -| 1.28 | ✅ done | 2026-07-23 | Frontend-Tests aktualisiert, tsc --noEmit OK (nur pre-existing Dms.tsx errors) | - -**Phase 1 Gesamt: ✅ Complete (Commits: 879106c, 5d79b4f, b15a62b)** -### Verifikation Phase 1 -- App startet OK (245 Routes) ✅ -- Python Syntax OK für alle geänderten Dateien ✅ -- Frontend: 251/265 Tests pass (14 pre-existing failures: Dms/Mail/ShareDialog) ✅ -- Backend-Tests: können nicht ausgeführt werden (kein PostgreSQL im Container) ⚠️ -- Keine verbleibenden company.* Events oder companies: Permissions ✅ - -## Phase 3: Plugin-UI-System -**Phase 3 Gesamt: ✅ Complete (Commit fc96a2f)** - -## Phase 3.5: Automation & Agents Plugin -**Phase 3.5 Gesamt: ✅ Complete (Commit 5dc6f29)** - -## Phase 4: KI-UI-Steuerung - -| # | Status | Datum | Was gemacht wurde | -|---|--------|------|-------------------| -| 4.1 | ✅ done | 2026-07-23 | UI-Command-Protokoll: JSON schema mit 6 command types (navigate, filter, open_contact, modal, tab, settings) in schemas.py | -| 4.2 | ✅ done | 2026-07-23 | WebSocket-Endpoint /ws/ai-ui-control + REST endpoints (POST /command, GET /command/{id}/status, GET /online-users) in ai_ui_control plugin | -| 4.3 | ✅ done | 2026-07-23 | useAIUIControl hook: WS client mit auto-reconnect, command dispatch, feedback sending | -| 4.4 | ✅ done | 2026-07-23 | Command: Navigate — useNavigate() für Route-Wechsel | -| 4.5 | ✅ done | 2026-07-23 | Command: Filter — URL-Search-Params + store pendingFilter | -| 4.6 | ✅ done | 2026-07-23 | Command: Open Contact — navigate zu /contacts/:id | -| 4.7 | ✅ done | 2026-23 | Command: Modal — store activeModal, ContactDetail syncs personModalOpen | -| 4.8 | ✅ done | 2026-07-23 | Command: Tab — store activeTab, ContactDetail syncs via useEffect | -| 4.9 | ✅ done | 2026-07-23 | Command: Settings — navigate zu /settings/:section + pendingSettings | -| 4.10 | ✅ done | 2026-07-23 | UI-Action-Feedback: sendFeedback via WS, store lastFeedback | -| 4.11 | ✅ done | 2026-07-23 | Visuelle KI-Indikation: AIUIControlIndicator component (Bot icon, toast, pulse animation) | -| 4.12 | ✅ done | 2026-07-23 | 18 Vitest tests: command protocol, store actions, feedback, visual indication | - -**Phase 4 Gesamt: ✅ Complete** - -### Verifikation Phase 4 -- TSC: 0 neue errors (nur 2 pre-existing Dms.tsx errors) ✅ -- Vitest: 18/18 AI UI Control tests pass ✅ -- Keine neuen Regressionen (AppShell tests waren pre-existing failing) ✅ -- Backend: ai_ui_control plugin mit WS + REST, service_container registration ✅ -- Frontend: useAIUIControl hook, aiUIControlStore, AIUIControlIndicator, i18n DE/EN ✅ -- Neue Dateien: 8 (plugin: __init__.py, plugin.py, routes.py, schemas.py, websocket_manager.py; frontend: store, hook, API, indicator, tests) ✅ - -## Phase 5: API-Vollständigkeit & Frontend-Anbindung - -### Batch 1 (Tasks 5.1-5.3) - -| # | Status | Datum | Was gemacht wurde | -||---|--------|------|-------------------| -| 5.1 | ✅ done | 2026-07-23 | API-Audit: docs/api-audit.md mit 158 UI-Funktionen in 24 Kategorien, alle per API erreichbar. 0 fehlende Endpoints. UI-State (Sidebar/Tab/Filter) durch Task 5.2 abgedeckt. 9 Tests (Audit-Dokument + Endpoint-Reachability) | -| 5.2 | ✅ done | 2026-07-23 | User-Preferences-API: Model (UserPreference mit TenantMixin), API Router (GET/PUT/DELETE /api/v1/user/preferences), Migration 0028, Frontend API + useUserPreferences hook mit uiStore-Sync, i18n DE/EN, 13 Backend-Tests (CRUD, Tenant-Isolation, CSRF, RBAC) | -| 5.3 | ✅ done | 2026-07-23 | Workflow-API-Frontend-Modul: frontend/src/api/workflows.ts mit TypeScript types + React Query hooks (CRUD, Instances, Advance/Cancel), 13 Frontend-Tests | - -**Phase 5 Batch 1 Gesamt: ✅ Complete** - -### Verifikation Phase 5 Batch 1 -- TSC: 0 neue errors (nur 2 pre-existing Dms.tsx errors) ✅ -- Backend Tests: 22/22 pass (13 user_preferences + 9 api_audit) ✅ -- Frontend Tests: 13/13 pass (workflows.test.ts) ✅ -- 3 Commits mit klaren Messages ✅ -- TenantMixin für neues DB-Model (UserPreference) ✅ -- RBAC (require_permission) für alle neuen API-Routes ✅ -- i18n (de.json, en.json) für Frontend-Änderungen ✅ -- Keine .env committet ✅ -- Bestehende Patterns verwendet: apiClient, Zustand stores, React Query hooks ✅ - -### Neue Dateien Phase 5 Batch 1 -- `docs/api-audit.md` — API-Audit-Dokument -- `app/models/user_preference.py` — UserPreference SQLAlchemy Model -- `app/routes/user_preferences.py` — User Preferences API Router -- `alembic/versions/0028_user_preferences.py` — Migration -- `frontend/src/api/userPreferences.ts` — Frontend API module -- `frontend/src/hooks/useUserPreferences.ts` — useUserPreferencesSync hook -- `frontend/src/api/workflows.ts` — Workflow API frontend module -- `frontend/src/api/__tests__/workflows.test.ts` — Workflow API tests -- `tests/test_user_preferences.py` — User preferences backend tests -- `tests/test_api_audit.py` — API audit tests - -### Modifizierte Dateien Phase 5 Batch 1 -- `app/main.py` — user_preferences router import + include_router -- `app/routes/__init__.py` — user_preferences import -- `app/core/permission_registry.py` — user_preferences:read/write permissions -- `app/core/permissions.py` — user_preferences in legacy role permissions -- `tests/conftest.py` — UserPreference model import + Contact seed fix (industry field) -- `frontend/src/i18n/locales/de.json` — userPreferences i18n -- `frontend/src/i18n/locales/en.json` — userPreferences i18n - ---- - -## Phase 5 Batch 2: Playwright E2E Tests - -| Task | Status | Datum | Notiz | -|---|---|---|---| -| 5.4 | ✅ done | 2026-07-23 | Playwright Setup: @playwright/test devDependency, playwright.config.ts (chromium, webServer, baseURL), e2e/helpers.ts mit Login/API-Mock/Fixtures | -| 5.5 | ✅ done | 2026-07-23 | auth.spec.ts: Login-Form rendered, successful login redirect, invalid credentials error, logout button, protected route redirect | -| 5.6 | ✅ done | 2026-07-23 | contact-crud.spec.ts: Company erstellen, Detail anzeigen+edit, Ansprechpartner hinzufügen, Kontakt löschen | -| 5.7 | ✅ done | 2026-07-23 | search.spec.ts: Search page rendered, search by contact name, grouped tabs, empty query, topbar search dropdown | -| 5.8 | ✅ done | 2026-07-23 | plugin-toggle.spec.ts: Settings plugins page, activate inactive plugin, deactivate active plugin, refresh button, install section | -| 5.9 | ✅ done | 2026-07-23 | mail.spec.ts: Mail page folder tree, mail list, open mail detail, mail settings add account form | -| 5.10 | ✅ done | 2026-07-23 | dms.spec.ts: DMS page source tree+explorer, create folder, upload section, file preview modal, share dialog | -| 5.11 | ✅ done | 2026-07-23 | calendar.spec.ts: Calendar page tree+view, create appointment, switch views (month/week/day), calendar tree, kanban columns, entry detail | - -**Phase 5 Batch 2 Gesamt: ✅ Complete** - -### Verifikation Phase 5 Batch 2 -- TSC: 0 neue errors (nur 2 pre-existing Dms.tsx onRangeSelect errors) ✅ -- 8 E2E spec files mit realistischen User-Workflows ✅ -- playwright.config.ts mit chromium project, webServer, trace/screenshot/video ✅ -- e2e/helpers.ts mit setupApiMocks (alle API endpoints gemockt), login/logout helpers, mock data fixtures ✅ -- test.describe Gruppierung + beforeEach Login-Setup in allen specs ✅ -- data-testid Attribute aus bestehenden Komponenten verwendet ✅ -- page.goto, page.locator, expect von @playwright/test ✅ -- Tests für CI/CD geschrieben (können nicht im Container laufen, kein PostgreSQL/Redis) ✅ - -### Neue Dateien Phase 5 Batch 2 -- `frontend/playwright.config.ts` — Playwright Konfiguration -- `frontend/e2e/helpers.ts` — Test-Helper (Login, API-Mocks, Fixtures) -- `frontend/e2e/auth.spec.ts` — Auth E2E Tests -- `frontend/e2e/contact-crud.spec.ts` — Contact CRUD E2E Tests -- `frontend/e2e/search.spec.ts` — Search E2E Tests -- `frontend/e2e/plugin-toggle.spec.ts` — Plugin Toggle E2E Tests -- `frontend/e2e/mail.spec.ts` — Mail E2E Tests -- `frontend/e2e/dms.spec.ts` — DMS E2E Tests -- `frontend/e2e/calendar.spec.ts` — Calendar E2E Tests - -### Modifizierte Dateien Phase 5 Batch 2 -- `frontend/package.json` — @playwright/test devDependency + e2e scripts - ---- - -## Phase 5 Batch 3 (Tasks 5.12-5.15) — ✅ Complete - -| Task | Status | Datum | Beschreibung | -|------|--------|-------|--------------| -| 5.12 | ✅ done | 2026-07-23 | API-Health-Check-Script (scripts/ai_health_check.py) — enumeriert 295 API-Routen, probt GET-Endpoints mit Auth-Token, JSON/CSV-Report, Exit-Codes | -| 5.13 | ✅ done | 2026-07-23 | CI/CD-Pipeline-Script (scripts/ai_deploy.py) — Build (docker/npm), Tests (pytest+vitest), Deploy (Coolify API), Rollback bei Fehler, --dry-run/--skip-tests/--skip-build | -| 5.14 | ✅ done | 2026-07-23 | API-Dokumentation vervollständigt — OpenAPI tags mit Beschreibungen (36 Tags), response_model für auth/health/users/notifications/system-settings, Pydantic examples, docs/api-documentation.md (500 Zeilen, 295 Endpoints) | -| 5.15 | ✅ done | 2026-07-23 | Automatisiertes Backup-System — scripts/backup.py (pg_dump+file backup, retention policy, notification), scripts/restore.py, system_notif plugin erweitert (backup.completed/failed events), backup config in system_settings | - -### Verifikation Phase 5 Batch 3 -- TSC: 0 neue errors (nur 2 pre-existing Dms.tsx onRangeSelect errors) ✅ -- 42 neue Tests alle passing ✅ -- Alle Scripts ausführbar (chmod +x) ✅ -- argparse für CLI-Argumente ✅ -- httpx für HTTP-Calls ✅ -- Keine .env committet ✅ -- Bestehende Patterns verwendet (APIRouter, Pydantic, sys.path.insert) ✅ - -### Neue Dateien Phase 5 Batch 3 -- `scripts/ai_health_check.py` — API Health Check Script (ausführbar) -- `scripts/ai_deploy.py` — CI/CD Deploy Script (ausführbar) -- `scripts/backup.py` — Automated Backup Script (ausführbar) -- `scripts/restore.py` — Restore Script (ausführbar) -- `docs/api-documentation.md` — Vollständige API-Dokumentation (295 Endpoints, 30 Tag-Gruppen) -- `tests/test_ai_health_check.py` — 7 Tests für Health Check -- `tests/test_ai_deploy.py` — 11 Tests für Deploy Script -- `tests/test_api_documentation.py` — 8 Tests für API-Dokumentation -- `tests/test_backup_restore.py` — 16 Tests für Backup/Restore - -### Modifizierte Dateien Phase 5 Batch 3 -- `app/main.py` — OpenAPI tags (36 Tags mit Beschreibungen), app description -- `app/routes/auth.py` — response_model für login/logout/me (AuthResponse, MessageResponse) -- `app/routes/health.py` — response_model HealthResponse -- `app/routes/notifications.py` — response_model UnreadCountResponse für unread-count -- `app/routes/users.py` — response_model für list/create/get (PaginatedUsers, UserResponse) -- `app/routes/system_settings.py` — response_model SystemSettingsResponse für GET/PUT -- `app/schemas/auth.py` — Field examples für LoginRequest, AuthResponse -- `app/schemas/user.py` — Field examples für UserCreate -- `app/schemas/system_settings.py` — Backup config fields (backup_interval, backup_retention_days, backup_destination) -- `app/plugins/builtins/ai_ui_control/routes.py` — tags=["ai-ui-control"] hinzugefügt -- `app/plugins/builtins/system_notif/plugin.py` — backup.completed/failed events + handler methods - -**Phase 5 Batch 3 Gesamt: ✅ Complete** - ---- - -## Phase 5 Batch 4: MCP Server & Client Integration (Tasks 5.16-5.17) ✅ - -### Task 5.16: MCP-Server Integration (10h) ✅ - -LeoCRM als MCP-Server: Externe Tools (Claude Desktop, andere KI-Clients) können auf LeoCRM-Daten zugreifen. - -**Neues Plugin `app/plugins/builtins/mcp_server/`:** -- `plugin.py` — PluginManifest (name='mcp_server', dependencies=['permissions'], permissions=['mcp:read','mcp:write']) -- `routes.py` — 3 Endpoints: - - `GET /api/v1/mcp/tools` — Listet alle 9 MCP-Tools mit Schema - - `POST /api/v1/mcp/tools/{tool_name}/execute` — Führt MCP-Tool aus (mit RBAC) - - `GET /api/v1/mcp/config` — MCP-Server-Konfiguration für externe Clients -- `tool_definitions.py` — 9 MCP-Tool-Definitionen: - - `search_contacts` — Kontakte durchsuchen (query, limit) - - `get_contact` — Kontakt Details abrufen (contact_id) - - `create_contact` — Neuen Kontakt erstellen (name, email, phone, type) - - `list_calendar_entries` — Kalendereinträge auflisten (date_from, date_to) - - `create_calendar_entry` — Termin erstellen (title, start, end) - - `list_emails` — E-Mails auflisten (folder, limit) - - `send_email` — E-Mail senden (to, subject, body) - - `list_files` — DMS-Dateien auflisten (folder_id) - - `upload_file` — Datei hochladen (filename, content_base64) -- `schemas.py` — Pydantic schemas (McpToolDefinition, McpToolExecuteRequest/Response, McpServerConfig) -- `migrations/0001_initial.sql` — Stateless plugin (no tables needed) -- Auth: Session-based auth + RBAC permission check per tool - -**Frontend:** -- `frontend/src/api/mcp.ts` — API client with React Query hooks (useMcpTools, useMcpConfig, useExecuteMcpTool) -- `frontend/src/pages/SettingsMcp.tsx` — MCP Settings page with tool listing, execution UI, and server config -- `frontend/src/routes/index.tsx` — Added /settings/mcp route -- `frontend/src/pages/Settings.tsx` — Added MCP nav item -- i18n: de.json + en.json updated with mcp.server.* and mcp.client.* keys - -**Tests:** `tests/test_mcp_server.py` — 7 tests (all passing) -- AC1: List MCP tools (9 tools) -- AC2: Get MCP config -- AC3: Execute search_contacts -- AC4: Non-existent tool returns 404 -- AC5: Tool definitions schema validation -- AC6: Unauthorized access rejected -- AC7: Execute create_contact - -### Task 5.17: MCP-Client Integration (6h) ✅ - -LeoCRM-Agenten können externe MCP-Server nutzen (Web-Search, Code-Execution, externe Datenquellen). - -**Neues Plugin `app/plugins/builtins/mcp_client/`:** -- `plugin.py` — PluginManifest (name='mcp_client', dependencies=['permissions'], permissions=['mcp-client:read','mcp-client:write','mcp-client:admin']) -- `models.py` — McpServerConfig Model (name, url, api_token, enabled, tenant_id) mit TenantMixin -- `routes.py` — CRUD + tool execution: - - `GET /api/v1/mcp-client/servers` — List server configs - - `POST /api/v1/mcp-client/servers` — Create server config - - `PATCH /api/v1/mcp-client/servers/{id}` — Update server config - - `DELETE /api/v1/mcp-client/servers/{id}` — Delete server config - - `GET /servers/{id}/tools` — List tools from external server - - `POST /servers/{id}/execute` — Execute tool on external server -- `schemas.py` — Pydantic schemas (McpServerConfigCreate/Update/Response, McpServerToolsResponse, McpServerExecuteRequest/Response) -- `client.py` — Async MCP client using httpx (list_tools, execute_tool, health_check) -- `tool_registry_integration.py` — Integriert externe MCP-Tools in ai_assistant tool_registry - - `sync_external_tools()` — Fetches tools from all enabled servers and registers them - - `unregister_all_external_tools()` — Cleanup - - Tool naming: `mcp__{server}__{tool}` -- `migrations/0001_initial.sql` — mcp_server_configs table - -**Frontend:** -- `frontend/src/api/mcpClient.ts` — API client with React Query hooks (useMcpServers, useCreateMcpServer, useUpdateMcpServer, useDeleteMcpServer, useMcpServerTools) -- MCP Client settings UI in SettingsMcp.tsx (server CRUD, tool viewing) -- i18n entries for mcp.client.* - -**Tests:** `tests/test_mcp_client.py` — 8 tests (all passing) -- AC1: List servers (empty) -- AC2: Create server config -- AC3: Update server config -- AC4: Delete server config -- AC5: List servers after creating -- AC6: Unauthorized access rejected -- AC7: Execute on non-existent server returns 404 -- AC8: Tool registry integration verification - -### Verifikation -- Alle 15 Tests passing (7 + 8) -- TSC: 0 neue Errors (nur pre-existing Dms.tsx errors) -- 2 Commits mit klaren Messages -- TenantMixin für McpServerConfig verwendet -- RBAC (require_permission) auf allen API-Routes -- i18n (de.json, en.json) aktualisiert -- Keine .env committet -- Bestehende Patterns verwendet (apiClient, React Query hooks, PluginManifest) - -**Phase 5 Batch 4 Gesamt: ✅ Complete** - ---- - -## Phase 5 Batch 5: Report Generator PDF-Support & Frontend (Tasks 5.18-5.19) - -### Task 5.18: Report Generator: PDF-Support & Druck-Funktionen ✅ - -**Backend:** -- WeasyPrint 69.0 installiert in /opt/venv -- 5 Jinja2 HTML-Templates erstellt in `app/plugins/builtins/report_generator/templates/`: - - `contact_list.html.j2` — Kontaktliste (Name, E-Mail, Telefon, Typ, Firma) - - `calendar_week.html.j2` — Wochenkalender (Tage × Stunden Grid) - - `calendar_month.html.j2` — Monatskalender (Grid mit Terminen) - - `company_list.html.j2` — Firmenliste (Name, Adresse, Ansprechpartner) - - `audit_log.html.j2` — Audit-Log (Timestamp, User, Action, Entity) - - Alle Templates: A4 Landscape, @media print CSS, Seitenränder, Seitenzahlen -- `schemas.py` erweitert: `output_format` um `pdf` und `print` ergänzt - - `PresetReportRequest` und `PresetReportInfo` Schemas hinzugefügt - - `ReportGenerateRequest` um optionales `output_format` erweitert -- `pdf_generator.py` erstellt: Jinja2 + WeasyPrint Pipeline - - `render_template_file()` — File-basierte Templates - - `render_template_string()` — User-defined Templates - - `generate_pdf()` / `generate_print_pdf()` — WeasyPrint PDF-Generierung - - `generate_preset_report()` — Preset-spezifische Generierung (PDF/CSV/Excel/JSON) - - `generate_pdf_from_template_content()` — User-template Generierung - - `PRESET_META` — Metadaten für 5 Preset-Berichte -- `routes.py` modifiziert: - - `GET /presets` — Listet alle Preset-Berichte - - `POST /presets/generate` — Generiert Preset-Bericht (StreamingResponse) - - `POST /generate` — Generiert User-Template-Bericht (StreamingResponse) - - Alle Endpunkte mit RBAC (`require_permission`: reports:read, reports:generate, reports:manage_templates) - - Generate-Endpunkte returnieren Datei direkt als StreamingResponse -- `plugin.py` permissions auf Colon-Format aktualisiert (reports:read, reports:generate, reports:manage_templates) - -**Tests:** `tests/test_report_generator.py` — 7 Tests (all passing) -- test_list_presets: GET /presets returns 5 presets -- test_generate_preset_pdf: PDF generation with valid %PDF- header -- test_generate_preset_csv: CSV generation with correct content -- test_create_and_generate_pdf_template: Template CRUD + PDF generation -- test_output_format_validation: Invalid format rejected (422) -- test_unauthenticated_access_blocked: 401 without auth -- test_viewer_cannot_manage_templates: RBAC 403 for viewer role - -### Task 5.19: Report Generator: Frontend-Oberfläche ✅ - -**Frontend:** -- `frontend/src/api/reports.ts` — React Query hooks: - - `useReportTemplates`, `useReportTemplate`, `useCreateReportTemplate`, `useUpdateReportTemplate`, `useDeleteReportTemplate` - - `useReportPresets`, `useGenerateReport`, `useGeneratePresetReport` - - `downloadBlob()` Helper für Browser-Download -- `frontend/src/pages/Reports.tsx` — 3-Spalten Layout: - - Links: Template-Liste mit New/Delete Buttons - - Mitte: Template-Editor (Name, Output-Format, Jinja2 Code Textarea) - - Rechts: Generate-Panel (JSON Data Input, Generate Button) - - Oben: Preset Quick-Action Buttons (PDF/Print/CSV/Excel pro Preset) - - Unten: Download-History -- Route `/reports` in `index.tsx` registriert (lazy-loaded) -- i18n Keys in `de.json` und `en.json` (reports.* Sektion + nav.reports) - -**Tests:** `frontend/src/pages/__tests__/Reports.test.tsx` — 5 Tests (all passing) -- renders page with preset quick actions -- displays templates in template list -- clicking new template shows editor -- selecting a template loads it into editor -- shows download history section - -### Verifikation -- Alle 12 Tests passing (7 backend + 5 frontend) -- TSC: 0 neue Errors (2 pre-existing Dms.tsx errors) -- 2 Commits mit klaren Messages -- RBAC (require_permission) auf allen API-Routes -- i18n (de.json, en.json) aktualisiert -- Keine .env committet -- Bestehende Patterns verwendet (apiClient, React Query hooks, lazy-loaded pages) - -**Phase 5 Batch 5 Gesamt: ✅ Complete** - ---- - -## Phase 5 Batch 6a (Tasks 5.20-5.22) — ✅ Complete - -### Task 5.20: Custom Fields — Plugin-Felder in UI (6h) - -**Backend:** -- `app/plugins/manifest.py` — `CustomFieldDefinition` model (name, label, label_key, field_type, options, default_value, required, entity) + `custom_fields` field on `PluginManifest` -- `app/routes/custom_fields.py` — `GET/PATCH /api/v1/contacts/{id}/custom-fields` routes - - Merges plugin-defined field definitions with stored values from `contacts.custom` JSONB - - Validates required fields, select/multiselect options - - RBAC: `contacts:read` / `contacts:write` -- `app/plugins/registry.py` — `get_active_manifests` now includes `custom_fields` in response - -**Frontend:** -- `frontend/src/api/customFields.ts` — `useCustomFields`, `useUpdateCustomFields` React Query hooks -- `frontend/src/components/contacts/CustomFieldRenderer.tsx` — renders fields by type (text/number/date/select/multiselect/boolean) in read + edit modes -- Integrated into `ContactDetail` (read-only) and `ContactEditModal` (editable with save logic) -- `frontend/src/store/pluginStore.ts` — `PluginCustomFieldDefinition` interface + `getCustomFieldsForEntity` selector - -**Tests:** -- `tests/test_custom_fields.py` — 9 tests (GET/PATCH/manifest validation) -- `frontend/src/__tests__/CustomFieldRenderer.test.tsx` — 5 tests (read/edit/multiselect/empty) - -### Task 5.21: Tasks-Plugin (12h) - -**Backend:** -- New plugin `app/plugins/builtins/tasks/` with full structure: - - `plugin.py` — PluginManifest (name='tasks', dependencies=['permissions'], permissions=['tasks:read/write/delete']) - - `models.py` — Task model with TenantMixin (title, description, status, priority, due_date, assigned_to, contact_id) - - `schemas.py` — Pydantic schemas for CRUD + assign + status - - `routes.py` — CRUD endpoints: GET/POST /tasks, GET/PATCH/DELETE /tasks/{id}, POST /tasks/{id}/assign, POST /tasks/{id}/status - - `services.py` — Business logic with filtering, pagination, soft-delete - - `migrations/0001_initial.sql` — Creates tasks table with indexes - - `jobs.py` — ARQ `tasks_due_reminder` cron job (daily 8:00) sends notifications for due tasks -- Registered in `app/core/worker.py` (functions + cron_jobs) -- Registered in `tests/conftest.py` - -**Frontend:** -- `frontend/src/api/tasks.ts` — Full React Query hooks (useTasks, useTask, useCreateTask, useUpdateTask, useDeleteTask, useAssignTask, useUpdateTaskStatus) -- `frontend/src/pages/Tasks.tsx` — Task list with filter (status/priority/search), create/edit modal, detail modal, pagination -- Route `/tasks` in `routes/index.tsx` (lazy-loaded) -- Sidebar entry via plugin manifest menu_items -- i18n keys for `nav.tasks` and `tasks.*` in de.json and en.json - -**Tests:** -- `tests/test_tasks.py` — 11 tests (list/create/update/status/delete + auth + validation) -- `frontend/src/__tests__/Tasks.test.tsx` — 3 tests (render/list/create modal) - -### Task 5.22: Saved Searches / Smart Lists (6h) - -**Backend:** -- `app/models/saved_filter.py` — SavedFilter model with TenantMixin (name, entity_type, filter_criteria JSONB, user_id) -- `app/routes/saved_filters.py` — GET/POST /saved-filters, DELETE /saved-filters/{id} with RBAC -- `alembic/versions/0029_saved_filters.py` — Migration creates saved_filters table -- Registered in `app/main.py` and `tests/conftest.py` - -**Frontend:** -- `frontend/src/api/savedFilters.ts` — useSavedFilters, useCreateSavedFilter, useDeleteSavedFilter hooks -- `frontend/src/components/SavedFilters.tsx` — Filter-builder UI with save button, load saved filters as tabs, delete -- Integrated into `ContactsListPage` as example (saves search/type/sort criteria) -- i18n keys for `savedFilters.*` in de.json and en.json - -**Tests:** -- `tests/test_saved_filters.py` — 9 tests (list/create/delete + auth + validation + duplicate) -- `frontend/src/__tests__/SavedFilters.test.tsx` — 3 tests (render/save modal/load filter) - -### Verifikation -- TSC: 0 neue Errors (2 pre-existing Dms.tsx errors) -- 3 Commits mit klaren Messages -- Mindestens 3 Tests pro Task (9+11+9 backend, 5+3+3 frontend) -- RBAC (require_permission) auf allen API-Routes -- TenantMixin auf allen neuen DB-Models -- i18n (de.json, en.json) aktualisiert -- Keine .env committet -- Bestehende Patterns verwendet (apiClient, React Query hooks, lazy-loaded pages, Zustand stores) -- Plugins automatisch via pkgutil entdeckt - -**Phase 5 Batch 6a Gesamt: ✅ Complete** - ---- - -## Phase 5 Batch 6b: Tasks 5.23–5.25 (Final Batch) - -### Task 5.23: Deduplication / Merge (6h) - -**Backend:** -- `app/models/contact_merge.py` — ContactMergeHistory model with TenantMixin (source_contact_id, target_contact_id, merged_fields JSONB, merged_by, note) -- `app/services/dedup_service.py` — Dedup service with find_duplicates (email/phone/name similarity), merge_contacts (field overrides, auto-merge, entity_links/tag_assignments re-pointing, soft-delete source), get_merge_history -- `app/routes/contacts.py` — Added endpoints: - - `POST /api/v1/contacts/duplicates` — find duplicates (RBAC: contacts:read) - - `POST /api/v1/contacts/merge` — merge two contacts (RBAC: contacts:write) - - `GET /api/v1/contacts/merge-history` — paginated merge history (RBAC: contacts:read) -- `alembic/versions/0030_contact_merge_history.py` — Migration creates contact_merge_history table -- Registered in `app/models/__init__.py` and `tests/conftest.py` - -**Frontend:** -- `frontend/src/api/dedup.ts` — useFindDuplicates, useMergeContacts, useMergeHistory hooks -- `frontend/src/components/contacts/DedupDialog.tsx` — UI for comparing and merging duplicate contacts with field selection -- i18n keys for `dedup.*` in de.json and en.json - -**Tests:** -- `tests/test_dedup.py` — 5 tests (find by email, find empty, merge success, merge same fails, merge history) - -### Task 5.24: PWA (Progressive Web App) (6h) - -**Setup:** -- `vite-plugin-pwa` installed in frontend -- `frontend/vite.config.ts` — VitePWA plugin with autoUpdate strategy, manifest (name, icons, theme_color), workbox config (static asset caching, font caching, StaleWhileRevalidate) - -**Assets:** -- `frontend/public/favicon.svg` — SVG favicon (blue rounded square with "L") -- `frontend/public/icon-192.svg` — 192x192 PWA icon -- `frontend/public/icon-512.svg` — 512x512 PWA icon - -**Frontend:** -- `frontend/src/components/PWAInstallPrompt.tsx` — Install prompt component with beforeinstallprompt event handling, dismiss/accept buttons, localStorage persistence -- `frontend/src/utils/notifications.ts` — Notification permission helper (getNotificationPermission, requestNotificationPermission, showNotification, isPWAInstalled) -- i18n keys for `pwa.*` in de.json and en.json - -**Tests:** -- `frontend/src/__tests__/PWAInstallPrompt.test.tsx` — 6 tests (no prompt, show prompt, dismiss, already dismissed, notification unsupported, isPWAInstalled) - -### Task 5.25: Dashboard-System ausbauen (8h) - -**Backend:** -- `app/routes/dashboard.py` — `GET /api/v1/dashboard/widgets` lists all dashboard widgets from active plugins (RBAC: dashboard:read) -- Uses existing `get_active_manifests()` from plugin registry which already includes `dashboard_widgets` -- Registered in `app/main.py` and `app/routes/__init__.py` - -**Frontend:** -- `frontend/src/api/dashboard.ts` — useDashboardWidgets hook -- `frontend/src/components/dashboard/DashboardWidgetLoader.tsx` — Dynamically loads widget components via lazy loading with fallback -- `frontend/src/components/dashboard/DashboardGrid.tsx` — CSS Grid layout with native HTML5 drag-and-drop widget reordering -- `frontend/src/pages/Dashboard.tsx` — Updated to include dynamic widget loading section -- 3 Example widgets: - - `RecentContactsWidget` — shows last 5 contacts - - `TasksSummaryWidget` — shows open/overdue/high-priority task counts - - `CalendarUpcomingWidget` — shows next 3 upcoming calendar entries -- i18n keys for dashboard widgets in de.json and en.json - -**Tests:** -- `tests/test_dashboard.py` — 3 backend tests (list widgets, auth required, plugin_name field) -- `frontend/src/__tests__/Dashboard.test.tsx` — 3 frontend tests (grid render, empty state, widget labels) - -### Verifikation -- TSC: 0 neue Errors (nur pre-existing Dms.tsx + FileExplorer errors) -- 3 Commits mit klaren Messages -- Mindestens 3 Tests pro Task (5+6+3 backend, 6+3 frontend) -- RBAC (require_permission) auf allen API-Routes -- TenantMixin auf allen neuen DB-Models (ContactMergeHistory) -- i18n (de.json, en.json) aktualisiert für alle Tasks -- Keine .env committet -- Bestehende Patterns verwendet (apiClient, React Query hooks, lazy loading) -- Plugins automatisch via pkgutil entdeckt -- dashboard_widgets bereits in PluginManifest (Phase 3) — genutzt in Task 5.25 - -**Phase 5 Batch 6b Gesamt: ✅ Complete** -**Phase 5 Gesamt: ✅ Complete** - ---- - -## Phase 6: React Hook Form + Zod überall - -| Task | Status | Datum | Notiz | -|---|---|---|---| -| 6.1 | ✅ done | 2026-07-24 | ComposeModal auf RHF + Zod: email list validation (to/cc/bcc), subject required, body via setValue. 4 validation tests. | -| 6.2 | ✅ done | 2026-07-24 | AppointmentModal auf RHF + Zod: title/calendar_id required, start Diese Datei ist der kompakte Fortschritts-Tracker für den Sanierungsplan. -> Der vollständige Sanierungsplan steht in `docs/ABSCHLUSSBERICHT_PHASE0_PHASE1.md`. -> Die Installationsanleitung steht in `docs/INSTALL.md`. - ---- - -## Phasen-Status - -| Phase | Status | Commit | Tests | Migration | -|-------|--------|--------|-------|----------| -| 0 — Ausgangsbasis | ✅ Abgeschlossen | v-phase0-baseline | — | — | -| 1 — Login, DB-Rollen, RLS | ✅ Abgeschlossen | 733fa1c | 35 Backend + 14 Plugin | 0085–0090 | -| 2 — Datenintegrität | ✅ Abgeschlossen | 745bc4f | FK-Tests auf Produktion | 0091 | -| 3 — Plugin-Lifecycle | ✅ Abgeschlossen | dfd9e77 | 14/14 pytest | — | -| 4 — KI-Delegation | ⏳ Nicht begonnen | — | — | — | -| 5 — Outbox | ✅ Abgeschlossen | 07a9997 | 18/18 pytest + Prod-Smoke | 0092 | -| 6 — Workspaces | ✅ Abgeschlossen | 310a9f0 | 25 Backend + 12 Frontend | 0072–0074 | -| 7 — DMS/Attachments | ⏳ Nicht begonnen | — | — | — | -| 8 — Sicherheitsreste | ⏳ Nicht begonnen | — | — | — | -| 9 — CI/Quality Gates | ⏳ Nicht begonnen | — | — | — | -| 10 — Backup/Monitoring/Pilot | ⏳ Nicht begonnen | — | — | — | - ---- - -## Abgenommene Gates (Phase 0+1) - -| Gate | Beschreibung | Status | -|------|-------------|--------| -| Gate 1 | Reproduzierbares Coolify-Deployment | ✅ | -| Gate 2 | Neuinstallation auf leerer Datenbank | ✅ | -| Gate 3 | Vollständiger Restore-Test | ✅ | -| Gate 4 | Passwort-Reset end-to-end | ✅ | -| Gate 5 | Worker und Eventhandler | ✅ | - ---- - -## Produktions-Setup - -### Coolify-Ressourcen - -| Ressource | UUID | Typ | -|-----------|------|------| -| API (crm.media-on.de) | dx4pqdziu4uj6x9fxs1u5z0x | Application | -| Worker | | Service | -| PostgreSQL | (Coolify Service) | Service | -| Redis | (Coolify Service) | Service | - -### Datenbankrollen - -| Rolle | Superuser | BYPASSRLS | Verwendung | -|-------|----------|-----------|------------| -| crm_user | Ja | Ja | Bootstrap (POSTGRES_USER) | -| crm_migration | Nein | Ja | Alembic + Plugin-Migrationen (DDL) | -| crm_auth | Nein | Nein | Login, Authentifizierung | -| crm_api | Nein | Nein | API-Abfragen | -| crm_worker | Nein | Nein | ARQ-Worker, Outbox | - -### Volumes - -| Volume | Verwendung | -|--------|------------| -| crm-postgres-data | PostgreSQL-Daten | -| crm-redis-data | Redis-Daten | -| dx4pqdziu4uj6x9fxs1u5z0x_storage | API + Worker Storage (geteilt) | - -### Deployment - -```bash -# Full deploy (API + Worker) über Coolify API -COOLIFY_API_TOKEN= python scripts/deploy.py - -# Nur Verifikation -COOLIFY_API_TOKEN= python scripts/deploy.py --verify-only - -# Nur Worker -COOLIFY_API_TOKEN= python scripts/deploy.py --worker-only -``` - ---- - -## Was erledigt ist - -### Phase 0+1 (Security & RLS) -- 5 DB-Rollen mit separaten Verbindungen -- RLS fail-closed auf 108 Tenant-Tabellen -- FORCE ROW LEVEL SECURITY aktiviert -- 0 legacy app.tenant_id Policies -- Plugin-Migrationen über crm_migration (DDL) -- Worker per-Tenant Outbox-Processing mit RLS-Kontext -- Event-Handler nur für aktive Plugins -- Passwort-Reset end-to-end mit SMTP getestet -- Leere DB-Installation ohne manuelle Eingriffe -- Restore + Upgrade verifiziert -- Coolify Redeploy/Stop/Start funktioniert ohne manuelles Eingreifen - -### Phase 2 (Datenintegrität) -- 74 FK-Constraints (tenant_id → tenants.id ON DELETE CASCADE) hinzugefügt -- 10 globale Tabellen ausgeschlossen -- Orphan-Cleanup durchgeführt -- FK-Tests auf Produktion: INSERT mit ungültiger tenant_id blockiert ✅ - -### Phase 3 (Plugin-Lifecycle) -- 14 Tests: Registry, Lifecycle, Idempotency, Dependencies, Core-Schutz -- Plugin-Lifecycle war bereits korrekt implementiert -- Tests bestätigen: activate → deactivate → reactivate funktioniert - ---- - -## Was als nächstes zu tun ist - -### Phase 5 (Outbox) — abgeschlossen (produktionsverifiziert) -- Per-Tenant Outbox-Processing (Gate 5) -- Dead-Letter-Queue: error_message + failed_at Spalten, Replay-Funktionen -- Monitoring: /api/v1/outbox/stats, /failed, /consumer-registry Endpoints -- Consumer-Registry: outbox_deliveries pro Consumer-Handler geschrieben -- Processing-Recovery: recover_stuck_events (stuck processing -> pending) -- Retention-Cleanup: cleanup_published_events (hourly cron job, 30 days) -- Replay setzt outbox_deliveries zurueck (clean retry) -- 23/23 Unit-Tests + Produktions-Verifikation: - - outbox_deliveries: 4 Eintraege mit status=delivered - - recover-stuck: 200, 0 stuck events - - cleanup-published: 200, 22 alte Events geloescht - - consumer-registry: 200, alle Handler gelistet - - failed: 200, 0 failed events - - stats: 200, korrekte counts -- deploy.py repariert: Worker-Deploy funktioniert jetzt korrekt - -### Phase 7 (DMS/Attachments) — nicht begonnen -- Streaming Upload/Download -- Deduplikation tenantlokal -- Keine Cross-Tenant-Dateireferenzen -- Aufwand: 10–16h - -### Phase 4 (KI-Delegation) — nicht begonnen -- Delegation-Contract, Tenant-scoped Permissions -- Audit, Rollback, Approval -- Aufwand: 10–16h - -### Phase 6 (Workspaces) — abgeschlossen (produktionsverifiziert) -- Backend: Widget CRUD (create, list, update, delete), Manager-Role-Check, Cross-Tenant-Validierung -- Default-Workspace Seeding (12 Standard-Module), Set-User-Default-Workspace -- Fix: create_workspace Default-Uniqueness (unset others before insert) -- Frontend: workspaceStore (Zustand) mit sessionStorage Persistenz -- API-Client Interceptor: X-Workspace-ID Header auf allen Requests -- useWorkspace hook auf workspaceStore umgestellt -- Widget API hooks: useWorkspaceWidgets, useCreateWorkspaceWidget, etc. -- Settings-Route: /settings/workspaces mit WorkspaceManagerPage -- 25 Backend-Tests + 12 Frontend-Tests (alle bestanden) -- Produktions-Verifikation: - - 2 Workspaces (Verkauf/Einkauf) mit unterschiedlichen Modulen ✅ - - Hidden module (calendar in Einkauf) nicht in Context ✅ - - Multiple widgets mit gleichem key (2x recent_contacts) ✅ - - Widget CRUD: create, update, delete ✅ - - Set-default: Workspace-Wechsel funktioniert ✅ - - Manager-Role: Creator ist Manager ✅ - - Cross-Tenant: RLS isoliert Workspaces pro Tenant ✅ - -### Phase 8–10 — nicht begonnen -- Sicherheitsreste, CI, Backup/Monitoring -- Aufwand: 38–66h - ---- -## Wichtige Dateien - -| Datei | Inhalt | -|-------|--------| -| `docs/ABSCHLUSSBERICHT_PHASE0_PHASE1.md` | Vollständiger Abschlussbericht + Sanierungsplan | -| `docs/INSTALL.md` | Vollständige Installationsanleitung | -| `docs/phase0_phase1_acceptance_report.md` | Abnahmeprotokoll Phase 0+1 | -| `scripts/deploy.py` | Coolify API Deployment-Skript | -| `scripts/seed_admin.py` | Admin-User erstellen | -| `docker-compose.yml` | Referenz-Compose (API + Worker + DB + Redis) | -| `.env.docker.example` | ENV-Template | -| `prestart.sh` | Container-Entrypoint (Migrationen + Rollen) | -| `worker.sh` | Worker-Entrypoint | - ---- - -## Wichtige Regeln für den nächsten Agenten - -1. **Keine manuellen Docker-Befehle** — alles über Coolify API oder deploy.py -2. **Repo lesen bevor ändern** — docker-compose.yml und deploy.py beachten -3. **Migrationen sind Forward-Only** — keine alten Migrationen verändern -4. **RLS ist fail-closed** — kein Tenant-Kontext = kein Zugriff -5. **crm_api hat keine DDL-Rechte** — Plugin-Migrationen über get_migration_engine() -6. **Worker ist Coolify Service** — UUID -7. **Alle DB-Passwörter sind identisch** — siehe .env.docker.example -8. **pgvector/pgvector:pg16** als DB-Image — nicht postgres:16-alpine -9. **Tests müssen mit echten unprivilegierten Rollen laufen** — nicht mit Superuser -10. **Jede Phase: analysieren → implementieren → migrieren → testen → dokumentieren** diff --git a/UMBAU_PLAN.md b/UMBAU_PLAN.md deleted file mode 100644 index c14b11a..0000000 --- a/UMBAU_PLAN.md +++ /dev/null @@ -1,1041 +0,0 @@ -# LeoCRM Architektur-Umbauplan — Komplett (V2) - -## Grundprinzip - -Architektur JETZT richtig stellen, bevor ERP-Module darauf aufbauen. Jede architektonische Änderung wird exponential teurer, sobald ERP-Module kommen. - -## Rolle der Workspaces - -Workspaces sind ausschließlich ein UI-, Navigations- und Arbeitskontext. - -Ein Workspace steuert: - -* welche Module und Menüpunkte angezeigt werden, -* welche Unterbereiche eines Moduls angezeigt werden, -* welche Kalender im Kalender-Modul sichtbar sind, -* welche Kontaktordner, gespeicherten Ansichten oder Filter angeboten werden, -* welche Dashboard-Widgets erscheinen, -* deren Reihenfolge und Konfiguration, -* den bevorzugten Arbeitskontext eines Benutzers. - -Ein Workspace verändert niemals: - -* RBAC-Berechtigungen, -* ABAC-Regeln, -* Entity Permissions, -* Owner- oder Sharing-Rechte, -* Tenant Memberships, -* RLS-Policies, -* tatsächliche Datenzugriffsrechte. - -Es gilt immer: - -```text -Tatsächlich sichtbare Daten -= -Workspace-Konfiguration -∩ -Berechtigungen des Benutzers -∩ -Objektzugriff -∩ -Tenant-Isolation -``` - -Ein Workspace darf niemals Rechte erteilen oder bestehende Rechte erweitern. - -Beispiel Kalender: - -```text -Im Workspace konfigurierte Kalender -∩ -Kalender, die der Benutzer lesen darf -= -im Kalender-Modul angezeigte Kalender -``` - -Beispiel Kontakte: - -```text -Im Workspace konfigurierte Kontaktordner/Ansichten -∩ -Kontakte, die der Benutzer lesen darf -= -im Kontakte-Modul dargestellte Inhalte -``` - -Dasselbe Kontakte-Modul darf gleichzeitig in mehreren Workspaces vorkommen, beispielsweise: - -* Workspace „Verkauf" -* Workspace „Einkauf" - -Beide Workspaces verwenden dasselbe Kontakte-Modul, aber mit unterschiedlichen: - -* Kontaktordnern, -* gespeicherten Ansichten, -* Standardfiltern, -* Dashboard-Widgets, -* Menükonfigurationen. - -Die bestehenden Rechte des Benutzers bleiben dabei unverändert. - -### Workspace darf kein Backend-Berechtigungsgate werden - -Ein API-Endpunkt darf nicht allein deshalb `403 Forbidden` liefern, weil ein Modul im aktuellen Workspace nicht angezeigt wird. - -Der Workspace-Kontext dient nur für: - -* UI-Konfiguration, -* Navigation, -* Default-Filter, -* Modulansichten, -* Kalenderauswahl, -* Ordnerauswahl, -* Widgetkonfiguration. - -Die tatsächliche Autorisierung erfolgt weiterhin über: - -```python -require_permission(...) -check_single_entity_access(...) -apply_visibility_filter(...) -RLS -``` - -Für workspacefähige Listenendpunkte kann der Workspace-Kontext als zusätzlicher Filter verwendet werden. Er ersetzt aber niemals einen Permission-Check. - -Direkte Links auf ein berechtigtes Fachobjekt dürfen weiterhin funktionieren, auch wenn das zugehörige Modul im aktuellen Workspace ausgeblendet ist. - -### Workspace-Kontext nicht global in Redis speichern - -Das erzeugt Probleme bei mehreren geöffneten Browser-Tabs. - -* Tab A arbeitet im Workspace „Verkauf". -* Tab B wechselt in „Einkauf". -* Durch eine globale Redis-Session würde Tab A ebenfalls ungewollt in „Einkauf" wechseln. - -Lösung: - -* Aktueller Workspace wird pro Browser-Tab im Frontend gespeichert (`sessionStorage` oder tablokaler Zustand). -* Der Client sendet bei workspacefähigen Requests: - -```http -X-Workspace-ID: -``` - -* Der Server validiert: Workspace gehört zum Tenant, Benutzer ist zugewiesen oder Admin, Workspace ist aktiv. -* In der Datenbank wird nur der bevorzugte Default-Workspace eines Benutzers gespeichert (`workspace_users.is_default`). -* Ein Workspacewechsel verändert keine Sessionberechtigungen. - -### Rollenmodell für Workspace-Verwaltung - -Workspaces haben keine eigenen Datenrechte. Trotzdem braucht ihre Konfiguration einen administrativen Verantwortungsbereich. - -#### System-Administrator - -Darf: alle Tenants verwalten, globale Plugins aktivieren/deaktivieren, alle Workspaces aller Tenants verwalten, globale Plattformkonfiguration ändern. - -#### Tenant-Administrator - -Darf innerhalb seines Tenants: Workspaces erstellen/ändern/löschen, Workspace-Manager bestimmen, Benutzer Workspaces zuweisen, Module und Widgets konfigurieren, tenantweit verfügbare Ressourcen auswählen. - -#### Workspace-Manager - -Keine globale RBAC-Rolle, sondern eine Zuweisung innerhalb eines konkreten Workspaces (`workspace_users.role = 'manager'`). - -Darf nur für seinen Workspace: Name/Beschreibung/Icon ändern, Module ein-/ausblenden, Reihenfolge ändern, Kalenderauswahl konfigurieren, Kontaktordner und Ansichten konfigurieren, Dashboard-Widgets konfigurieren, Benutzer zuweisen/entfernen (sofern Tenant-Mitglied). - -Darf nicht: Benutzerrechte ändern, Rollen/Gruppen ändern, RBAC/ABAC/Entity Permissions vergeben, Plugins aktivieren, Tenant-Einstellungen ändern, auf Daten zugreifen für die er keine normalen Rechte besitzt. - -#### Workspace-Mitglied - -Kann den Workspace benutzen, aber nicht konfigurieren. - -Permissions für Workspace-Verwaltung: - -```text -workspaces:read -workspaces:create -workspaces:update -workspaces:delete -workspaces:assign_users -workspaces:configure_modules -workspaces:configure_widgets -``` - -Für Workspace-Manager werden diese Rechte nicht tenantweit vergeben. Der Service prüft zusätzlich, ob der Benutzer im konkreten Workspace als `manager` eingetragen ist. - -## Aktueller Stand - -- P0 Fixes (6): Alle im Code, ungetestet -- P1 Fixes (9): Alle im Code, ungetestet -- Migrationen 0060-0067: In Produktion -- CI/CD Pipeline: 10 Quality Gates -- Frontend: canAccess Fallback (Workaround) -- RLS: Auf contacts + 30 Tabellen, aber überlappend mit Application Layer - -## Was NICHT umgesetzt wird - -- `security_resources` Tabelle — Aktuelles System (entity_permissions + owner_id + visibility.py) funktioniert. Lieber konsolidieren als neu bauen. -- Alle Services in Commands umbauen — Inkrementell, nicht Big-Bang. Neue Module nutzen Commands, alte bei Überarbeitung. -- `stored_objects` Tabelle — Stattdessen: Alles im DMS, Referenzen von Objekten. - ---- - -## Phase 0a: Beweise liefern (4h) - -**Ziel:** Beweisen dass die P0+P1 Fixes funktionieren. - -### Cross-Tenant Integrationstests (2h) -- Test: User A in Tenant 1 kann keine Daten von Tenant 2 sehen -- Test: RLS blockt Cross-Tenant Zugriff auf contacts, addresses, attachments, etc. -- Test: entity_permissions funktionieren nur innerhalb des gleichen Tenants -- Test: ABAC Policies sind tenant-scoped - -### RLS mit unprivilegierter Rolle testen (1h) -- Test: App läuft mit crm_runtime Rolle (nicht Superuser) -- Test: RLS blockt korrekt mit crm_runtime -- Test: set_tenant_context funktioniert mit unprivilegierter Rolle -- Test: Login funktioniert (Bootstrap-Zirkel gelöst) - -### Test-Suite grün (1h) -- pytest --collect-only: 1103 Tests sammelbar -- pytest tests/test_entity_permissions.py: Alle grün -- pytest tests/test_abac.py: Alle grün -- pytest tests/test_permission_performance.py: Alle grün -- Bestehende Tests: Soweit möglich grün - -**Abhängigkeit:** Keine — Sofort startbar - ---- - -## Phase 0b: Backup und Restore (2h) - -**Ziel:** Beweisen dass Backup und Restore funktionieren. - -### Backup Test (1h) -- pg_dump der Produktions-DB -- DMS/Object-Storage-Backup -- Restore in Test-DB -- Datensatzanzahlen vergleichen -- RLS Policies nach Restore prüfen -- entity_permissions nach Restore prüfen - -### Restore Test (1h) - -Restore-Reihenfolge: - -1. PostgreSQL-Backup wiederherstellen. -2. DMS/Object Storage wiederherstellen. -3. benötigte Secrets und Verschlüsselungsschlüssel bereitstellen. -4. `alembic current` prüfen. -5. `alembic upgrade head` ausführen. -6. App und Worker starten. -7. Login testen. -8. Datensatzanzahlen prüfen. -9. verwaiste Fremdschlüssel prüfen. -10. Tenant-Verteilung prüfen. -11. RLS- und Cross-Tenant-Tests ausführen. -12. DMS-Dateien stichprobenartig öffnen. - -`alembic stamp` ist nur für einen separat dokumentierten Sonderfall zulässig, wenn das vorhandene Schema vorher vollständig gegen die Zielrevision validiert wurde. - -**Abhängigkeit:** Phase 0a - ---- - -## Phase 1: Security Kernel konsolidieren (10h) - -**Ziel:** Ein eindeutiger Security Kernel mit klarer Verantwortungstrennung. - -### 1.1 Verantwortungstabelle (1h) - -| Schicht | Frage | Mechanismus | -|---------|-------|------------| -| Auth | Ist der User eingeloggt? | Session/Cookie | -| Tenant Membership | Ist User im richtigen Tenant? | UserTenant.status == 'active' | -| RBAC (Capabilities) | Darf User grundsätzlich Kontakte lesen? | `contacts:read` Permission | -| Objekt-ACL | Darf User DIESEN Kontakt sehen? | owner_id + entity_permissions | -| ABAC | Darf User Kontakte mit status=lead sehen? | entity_policies | -| RLS | Ist User im richtigen Tenant? (DB-Barriere) | `tenant_id = app.current_tenant_id` | - -### 1.2 RLS auf Tenant-Isolation reduzieren (3h) - -RLS soll NICHT die volle Geschäftsautorisierung übernehmen. Nur `tenant_id` Check. - -- Migration: Alle RLS Policies auf contacts reduzieren auf `tenant_id` Check -- Entfernen: owner_id, sharing, permissions aus RLS Policies -- Das macht die Application Layer (visibility.py) -- RLS = Sicherheitsgurt, nicht Fahrzeugsteuerung - -Neue contacts RLS Policies: -```sql -CREATE POLICY contacts_tenant_isolation ON contacts -FOR ALL -USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid) -WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::uuid); -``` - -### 1.3 visibility.py als SQL-Ausdrücke bestätigen (1h) - -- `apply_visibility_filter()` liefert SQLAlchemy-Ausdrücke — korrekt -- `check_single_entity_access()` für Einzelaktionen — korrekt -- Batch-Resolution für Listen — korrekt -- Keine Python-Filter, alles in SQL - -### 1.4 Überlappungen entfernen (2h) - -- RLS prüft nur tenant_id (Phase 1.2) -- visibility.py prüft owner_id + sharing + permissions -- entity_permission_service prüft effective_access -- Keine Redundanz mehr - -### 1.5 Frontend canAccess Fallback entfernen (3h) - -- Problem: Permissions werden im Frontend nicht korrekt geladen → canAccess Fallback zeigt alles -- Fix: Permissions beim Login laden und im authStore speichern -- `usePermission()` nutzt echte Permissions aus authStore -- canAccess Fallback wird entfernt — echte Permission-Checks -- Backend: `/api/v1/auth/me` returns permissions + field_permissions -- Frontend: authStore speichert permissions, usePermission nutzt sie - -**Abhängigkeit:** Phase 0a + 0b - ---- - -## Phase 2: Datenbankrollen und RLS strikt trennen (4h) - -**Ziel:** Korrekte DB-Rollen mit klaren Verantwortungen. Direkt nach Security Kernel, damit alle danach neu erstellten Tabellen sofort korrekte Owner, Grants, Default Privileges und RLS-Policies haben. - -### 2.1 Rollen definieren (1h) - -#### Plattformadministrator (nur einmalige Infrastruktur) - -```text -crm_platform_admin -``` - -Darf: Datenbank und Schema initialisieren, PostgreSQL-Erweiterungen installieren, Rollen erzeugen. Zugangsdaten stehen nicht dauerhaft in API- oder Worker-Containern. - -#### Migrationsrolle - -```text -crm_migration -``` - -* Owner des Anwendungsschemas -* führt Alembic aus -* darf DDL innerhalb des Anwendungsschemas -* kein Superuser -* kein API-Login - -#### Auth-Rolle - -```text -crm_auth -``` - -* minimaler Zugriff auf Benutzer, Tenants und aktive Memberships -* keine allgemeinen Fachdatenrechte - -#### API-Rolle - -```text -crm_api -``` - -* `NOSUPERUSER` -* `NOBYPASSRLS` -* kein Tabellenowner -* fachlicher Zugriff nur unter gesetztem Tenant-Kontext - -#### Worker-Rolle - -```text -crm_worker -``` - -Der Worker darf nicht pauschal alle Mandantendaten ohne Kontext lesen. - -Trennung: -* Polling/Claiming von Outbox-Jobs (ohne Tenant-Kontext) -* fachliche Verarbeitung eines konkreten Events (mit Tenant-Kontext, RLS erzwungen) - -### 2.2 Default Privileges (1h) - -Vollständige Default Privileges für: -* Tabellen -* Sequenzen -* Funktionen (sofern notwendig) -* Schema-Nutzung - -### 2.3 docker-compose + prestart.sh anpassen (1h) - -- API: `DATABASE_URL=postgresql+asyncpg://crm_api:...@postgres:5432/crm_db` -- Worker: `DATABASE_URL=postgresql+asyncpg://crm_worker:...@postgres:5432/crm_db` -- Migration: `MIGRATION_DATABASE_URL=postgresql+asyncpg://crm_migration:...@postgres:5432/crm_db` -- Auth: `AUTH_DATABASE_URL=postgresql+asyncpg://crm_auth:...@postgres:5432/crm_db` -- prestart.sh: Alembic mit crm_migration, App mit crm_api - -### 2.4 Eine Variable (1h) - -- `app.current_tenant_id` — einzige Variable -- `app.tenant_id` wird nicht mehr gesetzt (Legacy entfernt) -- Alle Migrationen die `app.tenant_id` nutzen werden auf `app.current_tenant_id` umgestellt -- Setze transaktionslokal: `SELECT set_config('app.current_tenant_id', :tenant_id, true)` -- Ohne Tenant-Kontext muss der fachliche Zugriff fehlschlagen - -**Abhängigkeit:** Phase 1 - ---- - -## Phase 3: Plugin-System vereinfachen (4h) - -**Ziel:** Router einmal registrieren, Aktivierungsstatus per Gate prüfen. - -### 3.1 Statische Registrierung nur in main.py (1h) - -- Alle Router beim Startup in main.py registrieren, einmalig -- PluginRegistry.activate() registriert keine Router mehr -- PluginRegistry.activate() ändert nur DB-Status + Permission Registry - -### 3.2 require_active_plugin als Gate mit Cache (1h) - -- Prüft: 1) Global aktiv (Registry), 2) Pro-Tenant aktiv (tenant_plugin_activation) -- Fail-closed bei Fehlern (503) -- WebSocket-Routen auch geprüft -- Cache: `Datenbank = Source of Truth, Redis = Cache` -- Cache-Key: `plugin-activation:{tenant_id}:{plugin_key}` -- Bei Aktivierung/Deaktivierung: DB aktualisieren, Cache invalidieren, Konfigurationsversion erhöhen -- Das Gate darf nicht bei jedem Request zwingend eine zusätzliche DB-Abfrage verursachen - -### 3.3 Aktivierung/Deaktivierung ohne Neustart (1h) - -- Gate prüft DB (über Cache), nicht in-memory Set -- Plugin aktivieren → DB Update → Cache invalidieren → Gate sieht es sofort -- Plugin deaktivieren → DB Update → Cache invalidieren → Gate blockt sofort - -### 3.4 tenant_plugin_activation UI (1h) - -- Settings → Plugins → pro-Tenant aktivieren/deaktivieren -- System-Admin kann global aktivieren -- Tenant-Admin kann pro-Tenant aktivieren (nur wenn global aktiv) - -**Abhängigkeit:** Phase 2 - ---- - -## Phase 4: Dateisysteme vereinheitlichen (6h) - -**Ziel:** Alles im DMS, Objekte referenzieren dorthin. - -### 4.1 entity_attachments Tabelle (1h) - -```sql -CREATE TABLE entity_attachments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - entity_type VARCHAR(50) NOT NULL, - entity_id UUID NOT NULL, - dms_file_id UUID NOT NULL REFERENCES dms_files(id) ON DELETE RESTRICT, - category VARCHAR(50), - display_name VARCHAR(200), - owner_id UUID REFERENCES users(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); -CREATE INDEX ix_entity_attachments_entity ON entity_attachments(entity_type, entity_id); -CREATE INDEX ix_entity_attachments_tenant ON entity_attachments(tenant_id); -``` - -`ON DELETE RESTRICT` — eine DMS-Datei darf nicht gelöscht werden, solange aktive Fachreferenzen existieren. - -### 4.2 Bestehende Attachments migrieren (1h) - -- Migration: Bestehende attachments → dms_files + entity_attachments -- Dateien bleiben im Storage, nur Metadaten werden migriert -- DMS File Eintrag pro bestehendem Attachment -- entity_attachments Referenz - -### 4.3 attachment_service.py umbauen (2h) - -- `save_attachment()`: Upload über DMS API, dann entity_attachments Eintrag -- `get_attachment()`: Lädt DMS File über Referenz -- `list_attachments()`: Lädt alle Referenzen für ein Entity -- `delete_attachment()`: Entfernt Referenz, DMS File Soft-Delete wenn keine weiteren Referenzen -- `download_attachment()`: Über DMS Storage-Backend - -### 4.4 DMS erweitern (1h) - -- Size-Limit: 50MB pro Datei -- MIME-Check: Erlaubte MIME-Types -- Hash: SHA-256 pro Datei -- Deduplikation: Nur tenantlokal — gleicher Hash innerhalb desselben Tenants → physische Deduplikation. Zwischen Tenants: eigener logischer Eintrag, eigener Audit-Trail, keine Offenlegung. -- Malware-Scan: Optional (ClamAV Integration später) - -### 4.5 Frontend (1h) - -- Upload: POST /api/v1/dms/files → dms_file_id → POST /api/v1/attachments -- Download: GET /api/v1/attachments/{id}/download → DMS File -- UI bleibt gleich, nur API-Calls ändern - -**Abhängigkeit:** Phase 3 - ---- - -## Phase 5: Workspaces Backend (47-81h) - -**Ziel:** Workspace Model + API + Migration + Modulkonfiguration. - -### 5.1 Datenmodell (4-7h) - -```sql -CREATE TABLE workspaces ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, - name VARCHAR(100) NOT NULL, - icon VARCHAR(50) DEFAULT 'LayoutGrid', - description TEXT, - is_default BOOLEAN NOT NULL DEFAULT FALSE, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_by UUID REFERENCES users(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE (tenant_id, id), - UNIQUE (tenant_id, name) -); - -CREATE UNIQUE INDEX uq_workspace_default_per_tenant -ON workspaces (tenant_id) -WHERE is_default = TRUE; -``` - -```sql -CREATE TABLE workspace_modules ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - workspace_id UUID NOT NULL, - module_key VARCHAR(100) NOT NULL, - is_visible BOOLEAN NOT NULL DEFAULT TRUE, - menu_order INTEGER NOT NULL DEFAULT 0, - config JSONB NOT NULL DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - FOREIGN KEY (tenant_id, workspace_id) - REFERENCES workspaces(tenant_id, id) - ON DELETE CASCADE, - UNIQUE (tenant_id, workspace_id, module_key) -); - -CREATE INDEX ix_workspace_modules_workspace -ON workspace_modules (tenant_id, workspace_id, menu_order); -``` - -```sql -CREATE TABLE workspace_users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - workspace_id UUID NOT NULL, - user_id UUID NOT NULL, - role VARCHAR(20) NOT NULL DEFAULT 'member', - is_default BOOLEAN NOT NULL DEFAULT FALSE, - assigned_by UUID REFERENCES users(id) ON DELETE SET NULL, - assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - FOREIGN KEY (tenant_id, workspace_id) - REFERENCES workspaces(tenant_id, id) - ON DELETE CASCADE, - FOREIGN KEY (tenant_id, user_id) - REFERENCES user_tenants(tenant_id, user_id) - ON DELETE CASCADE, - CHECK (role IN ('member', 'manager')), - UNIQUE (tenant_id, workspace_id, user_id) -); - -CREATE UNIQUE INDEX uq_workspace_default_per_user -ON workspace_users (tenant_id, user_id) -WHERE is_default = TRUE; -``` - -```sql -CREATE TABLE workspace_widgets ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - workspace_id UUID NOT NULL, - widget_key VARCHAR(100) NOT NULL, - title VARCHAR(200), - position_x INTEGER NOT NULL DEFAULT 0, - position_y INTEGER NOT NULL DEFAULT 0, - width INTEGER NOT NULL DEFAULT 1, - height INTEGER NOT NULL DEFAULT 1, - config JSONB NOT NULL DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - FOREIGN KEY (tenant_id, workspace_id) - REFERENCES workspaces(tenant_id, id) - ON DELETE CASCADE, - CHECK (position_x >= 0), - CHECK (position_y >= 0), - CHECK (width > 0), - CHECK (height > 0) -); - -CREATE INDEX ix_workspace_widgets_layout -ON workspace_widgets (tenant_id, workspace_id, position_y, position_x); -``` - -Kein UNIQUE Constraint auf `(workspace_id, widget_key)` — derselbe Widget-Typ muss beliebig oft vorkommen dürfen. - -### 5.2 Backend-Service und APIs (6-10h) - -- `app/models/workspace.py` — Workspace, WorkspaceModule, WorkspaceUser, WorkspaceWidget -- `app/services/workspace_service.py` — CRUD, Module-Zuweisung, User-Zuweisung, Workspace-Manager Validierung -- Default Workspace bei Tenant-Erstellung - -API: -``` -GET /api/v1/workspaces → Alle Workspaces (Admin) -POST /api/v1/workspaces → Workspace erstellen (Admin) -GET /api/v1/workspaces/{id} → Workspace Details -PUT /api/v1/workspaces/{id} → Workspace aktualisieren -DELETE /api/v1/workspaces/{id} → Workspace löschen -GET /api/v1/workspaces/my → Meine Workspaces -POST /api/v1/workspaces/{id}/modules → Module zuweisen -POST /api/v1/workspaces/{id}/users → User zuweisen -DELETE /api/v1/workspaces/{id}/users/{uid} → User entfernen -POST /api/v1/workspaces/{id}/widgets → Widget konfigurieren -``` - -### 5.3 Workspace-Manager und Validierung (4-7h) - -- `workspace_users.role = 'manager'` für Workspace-Manager -- Service prüft: Ist User Manager dieses Workspaces? Oder Tenant-Admin? Oder System-Admin? -- Workspace-Manager kann nur seinen Workspace konfigurieren -- Workspace-Manager kann keine RBAC/ABAC/Entity Permissions verändern - -### 5.4 Modulkonfiguration (5-8h) - -Jedes workspacefähige Modul definiert ein eigenes validiertes Konfigurationsschema in `workspace_modules.config`. - -Der Plugin-Code muss die Konfiguration validieren. Ungeprüfte beliebige JSON-Strukturen dürfen nicht direkt verwendet werden. - -#### Kalender-Beispiel - -```json -{ - "visible_calendar_ids": ["uuid-1", "uuid-2"], - "default_calendar_id": "uuid-1", - "show_unassigned_events": false -} -``` - -Das Kalender-Modul zeigt im Workspace nur: `visible_calendar_ids ∩ Kalender, die der Benutzer lesen darf`. - -APIs: -``` -GET /api/v1/workspaces/{workspace_id}/modules/calendar/config -PUT /api/v1/workspaces/{workspace_id}/modules/calendar/config -GET /api/v1/workspaces/{workspace_id}/modules/calendar/options -``` - -`options` liefert nur Kalender die zum Tenant gehören und auswählbar sind. Bei der normalen Abfrage wird zusätzlich der Benutzerzugriff geprüft. - -#### Kontakte-Beispiel - -```json -{ - "visible_folder_ids": ["uuid-vertrieb"], - "default_folder_id": "uuid-vertrieb", - "saved_filter_ids": ["uuid-offene-leads"], - "default_saved_filter_id": "uuid-offene-leads" -} -``` - -Im Workspace „Einkauf" kann dasselbe Kontakte-Modul mit anderer Konfiguration verwendet werden. - -#### Allgemeine Regel - -Jedes Plugin kann optional bereitstellen: -```python -workspace_config_schema -validate_workspace_config() -get_workspace_configuration_options() -apply_workspace_view_filter() -``` - -Plugins ohne Workspace-Unterstützung verwenden nur `is_visible` und `menu_order`. - -### 5.5 Kalender-Integration (4-7h) - -- Kalender-Modul nutzt `workspace_modules.config` für sichtbare Kalender -- `apply_workspace_view_filter()` filtert Kalender nach Workspace-Konfiguration ∩ Benutzer-Rechten -- Workspace-Manager kann Kalenderauswahl konfigurieren - -### 5.6 Kontakte-/Ansichten-Integration (3-6h) - -- Kontakte-Modul nutzt `workspace_modules.config` für sichtbare Ordner und Ansichten -- `apply_workspace_view_filter()` filtert Kontakte nach Workspace-Konfiguration ∩ Benutzer-Rechten -- Workspace-Manager kann Ordner und Ansichten konfigurieren - -### 5.7 Tests und Fehlerkorrekturen (6-10h) - -Freigabekriterien für Workspaces: - -1. Derselbe Benutzer kann in zwei Browser-Tabs unterschiedliche Workspaces verwenden. -2. Ein Workspacewechsel verändert keine Benutzerrechte. -3. Ein ausgeblendetes Modul erscheint nicht in der Sidebar. -4. Ein direkt aufgerufenes berechtigtes Fachobjekt bleibt erreichbar. -5. Ein Modul ohne Benutzerpermission wird auch dann nicht angezeigt, wenn es im Workspace aktiviert ist. -6. Kontakte können in mehreren Workspaces dargestellt werden. -7. Jeder Workspace kann unterschiedliche Kontaktordner und gespeicherte Ansichten verwenden. -8. Das Kalender-Modul zeigt nur konfigurierte und gleichzeitig berechtigte Kalender. -9. Nicht berechtigte Kalender werden durch Workspace-Konfiguration niemals sichtbar. -10. Derselbe Widget-Typ kann mehrfach im selben Workspace vorkommen. -11. Widget-Instanzen besitzen unabhängige Positionen und Konfigurationen. -12. Workspace-Manager können nur ihren Workspace konfigurieren. -13. Workspace-Manager können keine RBAC-, ABAC- oder Entity Permissions verändern. -14. Benutzer anderer Tenants können keinem Workspace zugewiesen werden. -15. RLS schützt alle Workspace-Tabellen tenantübergreifend. -16. Default-Workspace ist pro Benutzer eindeutig. -17. Default-Workspace ist pro Tenant eindeutig. -18. Gelöschte oder deaktivierte Workspaces können nicht mehr ausgewählt werden. - -**Abhängigkeit:** Phase 4 - ---- - -## Phase 6: Workspaces Frontend (in Phase 5 enthalten) - -**Ziel:** Workspace Switcher + UI + Sidebar-Filter + Dashboard. - -### 6.1 Workspace Switcher in TopBar (4-7h) - -- Dropdown neben Tenant-Switcher -- Zeigt alle Workspaces des Users -- Wechseln speichert active_workspace_id in `sessionStorage` (tablokal, nicht global) -- Client sendet `X-Workspace-ID` Header bei workspacefähigen Requests - -### 6.2 Sidebar-Filter nach Workspace (in 6.1 enthalten) - -Sidebar-Logik: - -```text -Menüpunkt sichtbar -= -Plugin global aktiv -UND -Plugin im Tenant aktiv -UND -Modul im Workspace sichtbar -UND -Benutzer besitzt grundlegende Read-Permission -``` - -Die Sidebar blendet aus: nicht aktive Plugins, im Workspace deaktivierte Module, Module ohne Benutzerberechtigung, leere Menügruppen. - -Reihenfolge aus `workspace_modules.menu_order`. - -### 6.3 Verwaltungsoberfläche (6-10h) - -- Settings → Rechte → Workspaces: Liste aller Workspaces -- Workspace erstellen/bearbeiten/löschen -- Module zuweisen (Checkbox-Liste aller verfügbaren Module) -- User zuweisen (Multi-Select) mit Rolle (member/manager) -- Modulkonfiguration (Kalenderauswahl, Kontaktordner, Ansichten) -- Dashboard-Widgets konfigurieren (Drag & Drop, mehrfach verwendbar) - -### 6.4 Dashboard und Mehrfach-Widgets (5-9h) - -- Dashboard lädt Widgets aus workspace_widgets -- Layout pro Workspace speichern -- Default-Widgets bei Workspace-Erstellung -- Derselbe Widget-Typ kann mehrfach vorkommen (z.B. "Umsatz aktueller Monat" + "Umsatz aktuelles Jahr") -- Widget-Instanzen besitzen unabhängige Positionen und Konfigurationen - -**Abhängigkeit:** Phase 5 - ---- - -## Phase 7: Outbox standardisieren (3h) - -**Ziel:** Standardisierter Event-Envelope mit Delivery-Tracking. - -### 7.1 Event-Envelope (1h) - -```json -{ - "event_id": "uuid", - "event_type": "crm.contact.created.v1", - "tenant_id": "uuid", - "aggregate_type": "contact", - "aggregate_id": "uuid", - "occurred_at": "timestamp", - "correlation_id": "uuid", - "schema_version": 1, - "payload": {} -} -``` - -### 7.2 outbox_deliveries Tabelle (1h) - -```sql -CREATE TABLE outbox_deliveries ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - event_id UUID NOT NULL REFERENCES event_outbox(id) ON DELETE CASCADE, - consumer_name VARCHAR(150) NOT NULL, - status VARCHAR(30) NOT NULL DEFAULT 'pending', - attempt_count INTEGER NOT NULL DEFAULT 0, - next_attempt_at TIMESTAMPTZ, - last_error TEXT, - processed_at TIMESTAMPTZ, - UNIQUE(event_id, consumer_name) -); -``` - -Beim Dispatch wird die für dieses Event erwartete Consumerliste festgeschrieben. Ein Event darf erst abgeschlossen werden, wenn alle verpflichtenden Deliveries erfolgreich sind. - -`consumer_inbox` bleibt zur Idempotenz bestehen. Ein Consumer muss anhand von `consumer_name + event_id` erkennen, ob er das Event bereits verarbeitet hat. - -### 7.3 Event-Namen standardisieren + Consumer-Inbox (1h) - -- Format: `crm.{aggregate}.{action}.v{version}` -- `enqueue_outbox_event()` bekommt Parameter für aggregate_type, aggregate_id, correlation_id -- Jeder Consumer trägt sich in consumer_inbox ein -- Event gilt als 'published' wenn alle Deliveries 'processed' sind -- Bei Consumer-Fehler: 'failed' Status, Event bleibt pending für Retry - -**Abhängigkeit:** Phase 6 - ---- - -## Phase 8: Command- und RequestContext-Grundlage (8h) - -**Ziel:** Eine Transaktionsgrenze pro Geschäftsoperation. INKREMENTELL. Muss vor den ersten ERP-Modulen stehen. - -### 8.1 Command Base Class + UnitOfWork (2h) - -```python -class Command(ABC): - @abstractmethod - async def execute(self, context: RequestContext, uow: UnitOfWork) -> Any: - ... - -class UnitOfWork: - def __init__(self, db: AsyncSession): - self.db = db - self.contacts = ContactRepository(db) - self.outbox = OutboxRepository(db) - self.audit = AuditRepository(db) - async def commit(self): - await self.db.commit() - async def rollback(self): - await self.db.rollback() -``` - -### 8.2 RequestContext (1h) - -```python -class RequestContext: - user_id: uuid.UUID - tenant_id: uuid.UUID - workspace_id: uuid.UUID | None - permissions: list[str] - is_system_admin: bool - correlation_id: uuid.UUID - def require(self, permission: str): - if not self.has_permission(permission): - raise PermissionError(permission) -``` - -### 8.3 Kern-Module auf Commands umstellen (3h) - -- CreateContact, UpdateContact, DeleteContact -- CreateAddress, UpdateAddress, DeleteAddress -- CreateAttachment, DeleteAttachment -- Ein Commit pro Operation: Route → Command → Audit + Outbox → Commit - -### 8.4 Bestehende Services belassen (2h) - -- Nicht alle Services gleichzeitig umbauen -- Bei Überarbeitung: Schrittweise auf Command Pattern migrieren -- Neue ERP-Module nutzen Commands von Anfang an - -**Abhängigkeit:** Phase 7 - ---- - -## Phase 9: Report-System isolieren (4h) - -**Ziel:** Reports in isoliertem Worker, nicht im API-Prozess. - -### 9.1 Report-Job Queue (1h) - -- Report-Erstellung als Background-Job (ARQ/Redis) -- API erstellt Job, gibt Job-ID zurück -- Client pollt Job-Status oder bekommt WebSocket-Notification - -### 9.2 Report-Worker (2h) - -- Separater Prozess für PDF-Erzeugung -- Constraints: Kein Shell-Zugriff, kein Docker-Socket, keine Secrets außer Template-Daten, Read-only Dateisystem (nur Output-Verzeichnis), CPU/RAM-Limit, nur freigegebene Templates, URL-Fetching deaktiviert - -### 9.3 PDF in Object Storage (1h) - -- Output in DMS, nicht im API-Container -- Job-Status: pending → processing → completed/failed -- Download-Link via DMS API - -**Abhängigkeit:** Phase 8 - ---- - -## Phase 10: Migrationen als Produktbestandteil (3h) - -**Ziel:** Automatische Tests für Migrationen. - -### 10.1 CI Gate: Alembic auf leerer DB (1h) - -- `alembic upgrade head` auf frischer DB muss funktionieren -- `alembic downgrade base` muss funktionieren -- CI bricht ab wenn Migration fehlschlägt - -### 10.2 Migration-Test-Script (1h) - -- Datensatzanzahlen vor/nach Migration vergleichen -- Verwaiste Fremdschlüssel prüfen -- Nullwerte prüfen -- Tenant-Verteilung prüfen -- RLS-Zugriffstest nach Migration - -### 10.3 Regeln (1h) - -- Veröffentlichte Migrationen nie nachträglich ändern -- Neue Revision für Fixes -- .gitignore erweitern: .env, dump.rdb, __pycache__, frontend/dist, .pytest_cache - -**Abhängigkeit:** Phase 9 - ---- - -## Phase 11: CI erweitern (4h) - -**Ziel:** CI muss architektonische Fehler stoppen. - -### 11.1 Fehlende CI Gates (2h) - -- **Ruff** (Python Linter) — Style + Import-Checks -- **Cross-Tenant Security Test** — Test der RLS Tenant-Isolation -- **Alembic Upgrade Test** — Auf leerer DB -- **Alembic Downgrade Test** — Base → head → base -- **Dependency Scan** — pip-audit für Python, npm audit für Frontend -- **Container Smoke Test** — App startet, Health-Check grün - -### 11.2 Build-Hygiene (1h) - -- **npm ci** statt `npm ci || npm install` — harter Abbruch bei Fehler -- **Python deps pinning** — requirements.txt mit exakten Versionen -- **.gitignore** — .env, dump.rdb, __pycache__, frontend/dist, .pytest_cache - -### 11.3 Vorhandene Gates bestätigen (1h) - -- Python Compile ✅, TypeScript ✅, Frontend Build ✅, Test Collection ✅, SQL Injection Check ✅, Jinja2 Sandbox ✅, RLS Variable ✅, Fail-Closed Plugin Gate ✅, Cross-Plugin Imports ✅, Alembic Heads ✅ - -**Abhängigkeit:** Phase 10 - ---- - -## Phase 12: Monitoring und Logging (3h) - -**Ziel:** Strukturiertes Monitoring für Pilotbetrieb. Extern abgesichert. - -### 12.1 Strukturiertes Logging (1h) - -- JSON-Logs für alle Requests -- Log-Level pro Environment konfigurierbar -- Correlation-ID in allen Logs -- Log-Rotation konfiguriert - -### 12.2 Health Endpoints + Metrics (1h) - -Trennung: - -```text -/health/live — Prüft ob der Prozess lebt -/health/ready — Prüft PostgreSQL, Redis, Worker, Storage -/metrics — Prometheus-kompatible Kennzahlen -``` - -### 12.3 Externes Alerting (1h) - -Interne Webhook-Alarme reichen nicht. Wenn API, Worker oder Redis ausgefallen sind, kann das System keinen eigenen Alarm versenden. - -Mindestens ein externes Monitoring: Uptime Kuma, Prometheus Alertmanager, Grafana, Sentry, oder Coolify Health Monitoring. - -Alarme bei: -- API nicht erreichbar -- Worker-Heartbeat fehlt -- Readiness rot -- Fehlerrate über Schwellwert -- Response-Zeit über Schwellwert -- DB-Pool erschöpft -- Outbox-Rückstau -- fehlgeschlagene Jobs - -**Abhängigkeit:** Phase 11 - ---- - -## Gesamt-Übersicht - -| Phase | Inhalt | Aufwand | -|-------|--------|---------| -| 0a | Cross-Tenant Tests + RLS Tests + Test-Suite grün | 4h | -| 0b | Backup/Restore Test | 2h | -| 1 | Security Kernel + canAccess Fallback entfernen | 10h | -| 2 | DB-Rollen strikt trennen + RLS standardisieren | 4h | -| 3 | Plugin-System vereinfachen + Cache | 4h | -| 4 | Dateisysteme vereinheitlichen (DMS) | 6h | -| 5 | Workspaces Backend + Frontend (komplett) | 47-81h | -| 7 | Outbox standardisieren + Deliveries | 3h | -| 8 | Command- und RequestContext-Grundlage | 8h | -| 9 | Report-System isolieren | 4h | -| 10 | Migrationen als Produktbestandteil | 3h | -| 11 | CI erweitern + Build-Hygiene | 4h | -| 12 | Monitoring und Logging (extern) | 3h | -| | **Gesamt** | **ca. 160-240h** | - -## Priorität vor ERP-Modulen - -**Zwingend vor ERP:** Phase 0a-5 (Tests + Security + DB-Rollen + Plugin + DMS + Workspaces) = 77-111h - -**Danach möglich:** ERP-Module können auf sauberer Architektur + Workspaces aufbauen. - -**Parallel zu ERP:** Phase 7-12 (Outbox + Commands + Reports + Migrationen + CI + Monitoring) = 25h - -## Abdeckung nach Plan-Abschluss - -| Kategorie | Vor Plan | Nach Plan | -|-----------|:---:|:---:| -| P0 Befunde (6) | 6/6 gefixt, ungetestet | 6/6 gefixt + getestet | -| P1 Befunde (9) | 9/9 gefixt, ungetestet | 9/9 gefixt + getestet | -| Architektur 10-Punkte | 2/10 | 10/10 | -| Mindestfreigabe 10-Punkte | 2/10 | 10/10 | -| Workspaces | 0 | Vollständig | -| Monitoring | 0 | Extern abgesichert | -| CI/CD | 10 Gates | 16 Gates | - -## Architekturbewertung - -```text -Aktuelle Architektur: ungefähr 6/10 -Nach erfolgreicher Umsetzung: ungefähr 8 bis 8,5/10 -Nach Pilotbetrieb und mehreren stabilen Releases: potenziell 9/10 -``` - -Ein Architekturwert von 10/10 ist nicht seriös messbar und vor einem realen Pilotbetrieb nicht belegbar. - -Der Plan gilt erst als abgeschlossen, wenn die Änderungen nicht nur im Code vorhanden, sondern durch reproduzierbare Integrationstests nachgewiesen sind. - -## Bewusst nicht umgesetzt - -- `security_resources` Tabelle — Aktuelles System funktioniert, konsolidieren statt neu bauen -- Alle Services in Commands — Inkrementell, nicht Big-Bang -- `stored_objects` Tabelle — Alles im DMS, Referenzen von Objekten diff --git a/alembic/versions/0085_restore_tenant_rls.py b/alembic/versions/0085_restore_tenant_rls.py index b678a08..527043f 100644 --- a/alembic/versions/0085_restore_tenant_rls.py +++ b/alembic/versions/0085_restore_tenant_rls.py @@ -1,6 +1,6 @@ """Restore tenant RLS, transfer ownership, fix roles and grants. -This migration implements Phase 1 of the Sanierungsplan: +This migration implements Phase 1 of the security hardening: 1. Transfer ALL table ownership from crm_user (SUPERUSER) to crm_migration (NOSUPERUSER, NOBYPASSRLS) 2. ALTER ROLE crm_migration NOBYPASSRLS diff --git a/architecture-feasibility-review.md b/architecture-feasibility-review.md deleted file mode 100644 index 57e7bea..0000000 --- a/architecture-feasibility-review.md +++ /dev/null @@ -1,448 +0,0 @@ -# LeoCRM — Architecture Feasibility Review - -**Project:** leocrm -**Reviewer:** Solution Architect (Agent Zero) -**Date:** 2026-06-28 -**Documents reviewed:** architecture.md (1939 lines), task_graph.json (965 lines, v2.0.0, 13 tasks), AGENTS.md (570 lines), requirements.md (2142 lines, 143 features) - ---- - -## VERDICT: FEASIBLE_WITH_RISKS - -The architecture is fundamentally sound — the tech stack is coherent, the multi-tenant design is well-structured, and the API design covers the frontend's needs. However, there are **3 CRITICAL issues** in the task graph that must be fixed before implementation can start, plus **5 MAJOR issues** that affect feasibility of individual tasks. - -| Severity | Count | -|----------|-------| -| CRITICAL | 3 | -| MAJOR | 5 | -| MINOR | 6 | - ---- - -## 1. TASK ORDERING - -### Finding: Minor inconsistency between execution_plan and parallelization_notes - -The dependency graph is correct: -- T01 → no dependencies (foundation) ✅ -- T02, T03 → depend on T01 only ✅ -- T07, T09, T10 → depend on T01 + T02 ✅ -- T04, T05, T06, T11 → depend on T01 + T03 ✅ -- T08a, T08b, T08c → depend on T07 + respective backend ✅ - -No hidden dependencies detected. T02 (Company/Contact) and T03 (Plugin Framework) are genuinely independent after T01. T09 does not depend on T03 (workflow engine uses event bus from T01, not plugin framework). T07 does not depend on T03 (frontend works without plugins — sidebar shows hardcoded v1 items). - -**Issue:** The `execution_plan` phases don't match the `parallelization_notes`: -- T09 is in Phase 3 (alone, `parallel: true`), T07 is in Phase 4 (`parallel: false`), T10 is in Phase 5 — but all three depend only on T01+T02 and could run simultaneously. -- The `parallelization_notes` correctly states T09 can run parallel with T07, and T10 can run parallel with T07/T09 — but the `execution_plan` structure implies sequential phases. -- **Recommendation:** Merge T09, T07, T10 into a single phase with `parallel: true`, or add explicit cross-phase parallelism annotations. - -**Risk level:** MINOR — the parallelization_notes clarify intent, but the execution_plan structure could mislead the orchestrator into sequential execution. - ---- - -## 2. TASK SIZING - -### Finding: T07 is critically oversized — MAJOR - -T07 (Frontend Core SPA) has: -- **38 requirement IDs** (highest of any task) -- **32 acceptance criteria** (highest of any task) -- **estimated_lines: 600** (wildly underestimated) - -Realistic line count breakdown: -| Component | Estimated Lines | -|-----------|----------------| -| Vite setup + App.tsx + Providers + Router | ~150 | -| API Client (axios, interceptors, error handling) | ~100 | -| Layout Shell (Sidebar, TopBar, ContentArea) | ~200 | -| Auth Pages (Login, Password-Reset Request+Confirm) | ~150 | -| Companies Feature (List + TanStack Table + Detail + Tabs + Form) | ~350 | -| Contacts Feature (List + Detail + Form) | ~300 | -| Settings Feature (Tree + Profile + Role Editor + User Mgmt) | ~250 | -| Audit Log Page | ~100 | -| Dashboard (Stat Cards + Recent Activity) | ~100 | -| Global Search Results Page | ~80 | -| i18n Setup (de/en locale files) | ~200 | -| Shared UI Component Library (12+ components) | ~500 | -| Tailwind CSS + Design Tokens | ~50 | -| **Total** | **~2530** | - -At ~2500 lines, T07 is 4× the estimated size and would overwhelm a single implementation block. - -**Recommendation:** Split T07 into: -- **T07a: Frontend Foundation** — Vite setup, App.tsx, API Client, Layout Shell, Auth Pages, UI Component Library, i18n, Tailwind, Routing (~1200 lines, ~15 ACs) -- **T07b: Frontend Feature Pages** — Companies, Contacts, Settings, Audit Log, Dashboard, Search (~1300 lines, ~17 ACs) -- T07b depends on T07a - -### Finding: T09 is borderline — MAJOR - -T09 combines two independent subsystems: -1. KI-Copilot (NL→API translation, LLM client, query/execute/history, RBAC enforcement) — ~350 lines -2. Workflow Engine (CRUD definitions, instances, step history, code-engine, event triggers, approval timeout) — ~450 lines - -Total: ~800 lines, 22 ACs. The two subsystems share no code — only both depend on T01+T02. - -**Recommendation:** Consider splitting into T09a (KI-Copilot) and T09b (Workflow Engine). They can run in parallel. If kept as one task, increase estimated_lines to 800 and ensure the delegation message clearly separates the two modules. - ---- - -## 3. TECH STACK FIT - -### Finding: Stack is coherent — no compatibility concerns - -| Component | Technology | Compatibility | -|-----------|-----------|---------------| -| Backend framework | FastAPI (async Python) | ✅ Native async, OpenAPI auto-gen | -| ORM | SQLAlchemy 2.0 async + asyncpg | ✅ Standard for FastAPI | -| Database | PostgreSQL 16 | ✅ MVCC, tsvector FTS, JSONB, UUID | -| Cache/Queue | Redis 7 | ✅ Sessions, caching, ARQ queue | -| Job queue | ARQ | ✅ Async-native, Redis-based, works with FastAPI | -| Migrations | Alembic | ✅ Standard for SQLAlchemy | -| Frontend framework | React 18 | ✅ Modern, concurrent features | -| Build tool | Vite | ✅ Fast HMR, ES modules | -| Server state | TanStack Query v5 | ✅ Works with React 18, Suspense | -| Client state | Zustand | ✅ Lightweight, complementary to TanStack Query | -| Forms | React Hook Form + Zod | ✅ Type-safe validation | -| Styling | Tailwind CSS | ✅ Utility-first, design tokens | -| Rich text | TipTap | ✅ React-compatible | -| PDF viewer | PDF.js | ✅ Mozilla, React-compatible | -| Document editing | OnlyOffice | ✅ External container, iframe integration | -| Testing (backend) | pytest + httpx | ✅ Standard for FastAPI | -| Testing (frontend) | Vitest + Testing Library | ✅ Standard for Vite/React | -| E2E | Playwright | ✅ Cross-browser, reliable | - -**Minor:** Python version not explicitly stated in architecture.md tech stack section. AGENTS.md mentions Python 3.12+ in conventions. Should be in the stack table. - -**Risk level:** NONE — stack is well-established and all components are known-compatible. - ---- - -## 4. PLUGIN ARCHITECTURE - -### Finding: Framework design is sufficient for v2 plugins — but with concerns - -T03's plugin framework provides: -- Plugin Registry (DB-backed) ✅ -- Manifest Schema (Pydantic) with endpoints, migrations, UI, events, services, preferences, notification_types ✅ -- Lifecycle Hooks (install/activate/deactivate/uninstall) ✅ -- Plugin DB Migration Runner with tenant_id validator ✅ -- UI Registry (routes, menu_items, detail_tabs, settings_pages, dashboard_widgets) ✅ -- Event Bus Integration ✅ -- Service Container Injection (db, cache, event_bus, storage, notifications) ✅ - -v2 plugin requirements vs. framework support: -| Plugin | Needs | Framework Support | -|--------|-------|-------------------| -| DMS | routes, events, migrations, UI, storage | ✅ All provided | -| Calendar | routes, events, migrations, UI, ARQ (reminders) | ✅ ARQ via container.jobs | -| Mail | routes, events, migrations, UI, IMAP/SMTP | ⚠️ IMAP/SMTP NOT in container — plugin implements its own | -| Tags | routes, migrations, UI | ✅ | -| Permissions | routes, migrations, UI | ✅ | - -**MAJOR concern:** T04 (DMS Backend) and T11 (Tags + Permissions + Entity Links Backend) have **identical acceptance criteria** for these endpoints: -- `GET /api/v1/dms/files/{id}/permissions` -- `POST /api/v1/dms/files/{id}/link` -- `DELETE /api/v1/dms/files/{id}/link` -- `POST /api/v1/dms/files/{id}/share-link` -- `GET /api/public/share/{token}` mit expired link → 410 -- `DMS plugin listens to company.deleted event → linked files cleanup` -- `Folder permissions enforced: user without read → 403` - -Both tasks are scheduled in Phase 6 (parallel). Two implementers would write the same endpoints → merge conflict. - -Additionally, T04's test_spec runs `tests/test_tags.py` with `--cov=app/plugins/builtins/tags` — but Tags are T11's responsibility, not T04's. - -**Recommendation:** Clearly separate responsibilities: -- T04: DMS folders, files, upload, preview, OnlyOffice, bulk operations, DMS search -- T11: Tags CRUD + assignment, Permissions (file_shares, folder_permissions, share_links), Entity links (file_links) -- Remove duplicated ACs from one task (keep in the task that owns the endpoint) -- Remove `tests/test_tags.py` from T04's test_spec - -**Risk level:** MAJOR — parallel execution with overlapping ACs will cause implementation conflicts. - ---- - -## 5. API DESIGN - -### Finding: Endpoints are sufficient for T07 — one minor gap - -T07 frontend needs vs. available endpoints: -| Frontend Need | API Endpoint | Available In | -|---------------|-------------|-------------| -| Login/logout/me | `/api/v1/auth/*` | T01 ✅ | -| Password reset | `/api/v1/auth/password-reset/*` | T01 ✅ | -| Tenant switch | `/api/v1/auth/switch-tenant` | T01 ✅ | -| User CRUD | `/api/v1/users` | T01 ✅ | -| Role CRUD | `/api/v1/roles` | T01 ✅ | -| User settings | `/api/v1/users/me/settings` | T01 ✅ | -| Company CRUD + search + export | `/api/v1/companies` | T02 ✅ | -| Contact CRUD + search + export | `/api/v1/contacts` | T02 ✅ | -| Company-Contact N:M links | `/api/v1/companies/{id}/contacts/{cid}` | T02 ✅ | -| Import/preview | `/api/v1/import`, `/api/v1/import/preview` | T02 ✅ | -| Audit log | `/api/v1/audit-log` | T01 ✅ | -| Notifications | `/api/v1/notifications` | T01 ✅ | -| Global search | `/api/v1/search` | T01/T02 ✅ | -| Health | `/api/v1/health` | T01 ✅ | -| Dashboard stats | **MISSING** | ❌ | -| Plugin menu items | `/api/v1/plugins` | T03 (not a dependency, but done earlier) ✅ | - -**MINOR gap:** T07's AC says "Dashboard renders with stat cards + recent activity" but there is no `GET /api/v1/dashboard` or `GET /api/v1/stats` endpoint. The frontend can compose this from existing endpoints (count companies, count contacts, recent audit log), but an aggregated endpoint would be cleaner. - -**Recommendation:** Either add a `GET /api/v1/dashboard` endpoint to T01 or T02, or document that the dashboard composes from `GET /api/v1/companies?page_size=1` (for total count) + `GET /api/v1/contacts?page_size=1` + `GET /api/v1/audit-log?page_size=10`. - -Also: T02 has AC `GET /api/v1/companies/{id}/emails → 200 (empty array wenn mail plugin inactive)` — this is a v2 endpoint stub in a v1 task. The implementer needs to know to return an empty array when the mail plugin is not active. This should be explicitly documented as a conditional stub. - ---- - -## 6. DATABASE SCHEMA - -### Finding: MAJOR — users table has FK references to v2 tables that don't exist in v1 - -The `users` table includes: -``` -default_calendar_id | UUID | FK→calendars.id NULL -default_mail_account_id | UUID | FK→mail_accounts.id NULL -``` - -`calendars` and `mail_accounts` are v2 plugin tables created by T05 and T06 respectively. In v1, these tables don't exist. The Alembic initial migration (T01) would fail trying to create FK constraints to non-existent tables. - -**Recommendation:** -1. Remove `default_calendar_id` and `default_mail_account_id` from the initial v1 migration -2. Add these columns in v2 plugin migrations (T05 adds `default_calendar_id`, T06 adds `default_mail_account_id`) with FK constraints -3. Or: add the columns without FK constraints in v1, add FKs in v2 migrations - -### Other schema findings: - -- `plugin_migrations` table: described in text (line 1205) but not formally defined as a table with columns. MINOR — should be in the schema section. -- `groups` table: referenced by `folder_permissions.group_id`, `file_shares.group_id`, `calendar_shares.group_id` but not defined anywhere. This is a v2 concern but should be documented. MINOR. -- No `user_groups` or `groups` table in the schema — several v2 features reference group-based sharing. MAJOR for v2, but not blocking for v1. -- All core tables have proper indexes, tenant_id, timestamps, and soft-delete where appropriate. ✅ -- FTS design with tsvector + GIN is correct. ✅ -- UUID PKs with `gen_random_uuid()` — correct for PostgreSQL 16. ✅ - ---- - -## 7. FRONTEND ARCHITECTURE - -### Finding: Detailed enough for T07 implementation — MINOR gaps - -The frontend architecture section covers: -- Full stack table (React 18, Vite, React Router v6, TanStack Query, Zustand, RHF+Zod, Tailwind, etc.) ✅ -- Complete routing table with all routes ✅ -- State management strategy (TanStack Query / Zustand / URL state) ✅ -- i18n setup (de/en, locale files, date-fns) ✅ -- Accessibility (ARIA, 44px, reduced-motion, sr-only, keyboard, WCAG 2.1 AA) ✅ -- Design system based on approved prototype ✅ -- Frontend directory structure ✅ - -Missing details (MINOR): -- No API client design (interceptor pattern, error normalization, 401 redirect logic) — mentioned in T07 description but not in architecture.md -- No data flow diagram (how TanStack Query hooks connect to API client → backend) -- PluginRegistry.tsx / PluginLoader.tsx described conceptually but no implementation contract -- No specific file-per-feature breakdown (e.g., what files go in `features/companies/`) - -These gaps are fillable from AGENTS.md conventions and the T07 task description. Not blocking. - -**Risk level:** LOW — architecture + AGENTS.md conventions provide sufficient guidance. - ---- - -## 8. KI-COPILOT + WORKFLOW ENGINE - -### Finding: T09 is under-scoped on requirements but over-scoped on acceptance criteria — MAJOR - -T09 has only **5 requirement IDs** but **22 acceptance criteria**: -- F-AI-01 → covers entire AI Copilot subsystem (query, execute, history, RBAC, audit, tenant isolation, field permissions) = 8 ACs -- F-WF-01 → covers entire Workflow Engine (definition CRUD, instances, advance/approve/reject/cancel, event triggers, step history, code-engine, approval timeout) = 14 ACs -- F-CORE-01 → event bus (shared with T01) -- F-CORE-06 → API-first (architectural principle, not a feature) -- F-TEST-01 → testing (shared across all tasks) - -The 5 reqs is misleading — F-AI-01 and F-WF-01 are each complex subsystems masquerading as single features. The 22 ACs are well-defined and testable, but implementing two independent subsystems in one delegation is risky. - -**The two subsystems share no code:** -- KI-Copilot uses: ai_conversations model, LLM client, user session (T01), audit log (T01) -- Workflow Engine uses: workflows/instances/step_history models, event bus (T01), ARQ (T01) -- They don't reference each other - -**Recommendation:** Split T09 into: -- **T09a: KI-Copilot API** — ai_conversations, LLM client, query/execute/history, RBAC enforcement (~350 lines, 8 ACs) -- **T09b: Workflow Engine** — workflow CRUD, instances, code-engine, event triggers, approval timeout (~450 lines, 14 ACs) -- Both depend on T01+T02, can run in parallel - -If kept as one task, increase estimated_lines from 700 to 800+ and ensure the delegation message explicitly separates the two modules with clear file boundaries. - ---- - -## 9. DEPLOYMENT READINESS - -### Finding: Architecture supports Docker/Coolify deployment — MINOR gaps - -Present in architecture: -- Docker Compose with all 6 services (backend, frontend, postgres, redis, worker, onlyoffice) ✅ -- Health checks for backend container ✅ -- Environment variables documented with .env.example plan ✅ -- Backup strategy (pg_dump daily + storage backup) ✅ -- Named volumes for persistent data ✅ -- Non-root container user (app:app) in AGENTS.md forbidden patterns ✅ -- Structured JSON logging for observability ✅ -- Prometheus metrics endpoint planned ✅ - -Missing (MINOR): -- No Dockerfiles (backend/frontend) — implementation concern (T01/T07), not architecture -- No Coolify-specific configuration — Coolify can use docker-compose directly, but no coolify.json or resource limits documented -- No SSL/TLS documentation — Coolify uses Traefik for LE certificates, but this isn't stated in architecture -- No resource limits (CPU/memory) for containers — important for multi-tenant production -- No logging driver configuration for Docker — structured logging is at app level, but Docker log rotation isn't mentioned -- OnlyOffice container is always in docker-compose — should use Docker Compose profiles for optional services -- No database initialization script (create DB, run initial migration) documented in deployment flow - -**Risk level:** LOW — all gaps are deployment-phase concerns, not architecture blockers. T10 covers documentation. - ---- - -## 10. v1/v2 BOUNDARY - -### Finding: Boundary is mostly clean — one MAJOR schema issue, one MINOR stub issue - -### v1 can ship standalone: ✅ (with fix) - -| v1 Task | Standalone? | Notes | -|---------|------------|-------| -| T01 (Core+Auth) | ✅ | Plugin framework ready, no plugins installed | -| T02 (Company+Contact) | ✅ | Full CRM without plugins | -| T03 (Plugin Framework) | ✅ | Framework ready, zero plugins activated | -| T07 (Frontend Core) | ✅ | Companies, contacts, settings, dashboard — no plugin UIs | -| T09 (KI-Copilot+Workflows) | ✅ | AI + workflows work on core entities | -| T10 (Monitoring+Docs) | ✅ | Health, metrics, docs for v1 scope | - -### Issues affecting v1 standalone: - -**MAJOR (blocking):** `users` table FK references to v2 tables (`calendars.id`, `mail_accounts.id`) — v1 migration fails. (See §6) - -**MINOR:** T02 has AC `GET /api/v1/companies/{id}/emails → 200 (empty array)` — this is a v2 mail endpoint stub. The v1 implementer must handle this gracefully (return empty array when mail plugin inactive). Should be documented as a conditional stub, not a full endpoint implementation. - -**MINOR:** T07 company detail shows "Files placeholder, Emails placeholder" tabs — clean v1/v2 boundary via placeholder tabs. ✅ - -### v1/v2 separation in task graph: -- v1 tasks: T01, T02, T03, T07, T09, T10 (6 tasks) ✅ -- v2 tasks: T04, T05, T06, T11, T08a, T08b, T08c (7 tasks) ✅ -- No v1 task depends on a v2 task ✅ -- No v2 task is required for v1 to function ✅ -- Feature coverage: 73 v1 features / 70 v2 features — all covered ✅ - ---- - -## CRITICAL ISSUES (must fix before implementation) - -### C1: T04/T11 acceptance criteria overlap - -**Problem:** T04 (DMS Backend) and T11 (Tags+Permissions+Links Backend) have 7 identical acceptance criteria for the same endpoints (permissions, file links, share-links, event cleanup, folder permissions). Both are in Phase 6 (parallel) → two implementers writing the same code. - -T04's test_spec also includes `tests/test_tags.py` and `--cov=app/plugins/builtins/tags` — Tags are T11's responsibility. - -**Fix:** -- T04 owns: DMS folders, files, upload, preview, OnlyOffice, bulk ops, DMS search -- T11 owns: Tags CRUD + assignment + bulk, Permissions (file_shares, folder_permissions, share_links), Entity links (file_links), public share endpoint -- Remove duplicated ACs from T04 -- Remove `tests/test_tags.py` and tags coverage from T04's test_spec - -### C2: v2 tasks have non-compliant test_spec - -**Problem:** T08a, T08b, T08c, T11 have `test_spec` as plain strings instead of structured objects: -```json -"test_spec": "Component tests for file browser, upload, share dialog, tag picker." -``` -Instead of the mandatory structure: -```json -"test_spec": { - "commands": [...], - "expected_results": "...", - "test_files": [...], - "coverage_target": 80 -} -``` - -**Fix:** Convert all 4 tasks' test_spec to structured objects with commands, test_files, expected_results, coverage_target. - -### C3: v2 tasks use invalid subagent profiles - -**Problem:** -- T08a, T08b, T08c use `"subagent_profile": "frontend_dev"` — this profile does not exist -- T11 uses `"subagent_profile": "backend_dev"` — this profile does not exist -- Available profiles: `implementation_engineer`, `developer`, etc. - -**Fix:** Change all v2 tasks to `"subagent_profile": "implementation_engineer"` (matching v1 tasks and AGENTS.md). - ---- - -## MAJOR ISSUES (should fix before implementation) - -### M1: T07 oversized (38 reqs, 32 ACs, ~2500 lines estimated vs 600 stated) -Split into T07a (Frontend Foundation) + T07b (Frontend Feature Pages). - -### M2: users table FKs to v2 tables (calendars.id, mail_accounts.id) -Remove from v1 migration, add in v2 plugin migrations. - -### M3: T09 combines two independent subsystems (KI-Copilot + Workflow Engine, 22 ACs) -Split into T09a (KI-Copilot) + T09b (Workflow Engine), or increase estimated_lines and ensure clear module separation in delegation. - -### M4: T08a/T08c acceptance criteria are mixed -T08a (DMS+Tags+Permissions) has a Mail AC ("shared mailbox selector"). T08c (Mail+Search) has a DMS AC ("DMS route /dms renders file browser") and a Docker Compose AC. Reassign ACs to correct tasks. - -### M5: T04 test_spec includes tags tests (T11's responsibility) -Remove `tests/test_tags.py` and `--cov=app/plugins/builtins/tags` from T04's test_spec. - ---- - -## MINOR ISSUES (nice to fix, non-blocking) - -### m1: Execution plan phases don't reflect parallelization notes -T09/T07/T10 are in separate phases but could run parallel. Merge or annotate. - -### m2: No dashboard/stats endpoint for T07's dashboard stat cards -Add `GET /api/v1/dashboard` or document client-side composition. - -### m3: plugin_migrations table not formally defined in schema section -Add table definition with columns. - -### m4: No Coolify-specific deployment config or SSL/TLS documentation -Document Traefik SSL termination and Coolify deployment flow. - -### m5: OnlyOffice container always in docker-compose -Use Docker Compose profiles for optional services. - -### m6: Python version not in tech stack table -Add Python 3.12+ to the stack table in architecture.md §1. - ---- - -## TOP 3 RISKS - -1. **T07 oversized task** — 38 reqs / 32 ACs / ~2500 lines in one delegation. High probability of incomplete implementation, context window exhaustion, or quality degradation. Must split. - -2. **T04/T11 endpoint overlap** — 7 identical ACs across two parallel tasks. Will cause merge conflicts, duplicate code, and test failures when both implementers write the same endpoints. - -3. **users table FK to non-existent v2 tables** — v1 Alembic migration will fail on `FK→calendars.id` and `FK→mail_accounts.id` because those tables don't exist until v2 plugin installation. - ---- - -## RECOMMENDATION FOR PHASE 3 START - -**Conditional GO — fix 3 CRITICAL issues first, then start implementation.** - -Required actions before implementation: -1. Fix C1: Separate T04/T11 endpoint ownership, remove duplicate ACs -2. Fix C2: Convert T08a/T08b/T08c/T11 test_spec to structured objects -3. Fix C3: Change invalid subagent profiles to `implementation_engineer` -4. Fix M1: Split T07 into T07a + T07b -5. Fix M2: Remove v2 FKs from users table in v1 migration -6. Fix M4: Reassign mixed ACs between T08a and T08c -7. Fix M5: Remove tags tests from T04 test_spec - -Recommended (non-blocking): -- Fix M3: Split T09 into T09a + T09b (reduces delegation risk) -- Fix m1-m6 for documentation quality - -After fixes: task_graph.json v2.1.0, architecture.md v1.1, then proceed to implementation. diff --git a/codebase-vs-requirements.md b/codebase-vs-requirements.md deleted file mode 100644 index c14bde1..0000000 --- a/codebase-vs-requirements.md +++ /dev/null @@ -1,176 +0,0 @@ -# LeoCRM — Codebase vs Requirements (IST-Stand Juli 2026) - -**Datum:** 2026-07-23 -**Prüfer:** Agent Zero -**Methode:** Vollständige Code-Inspektion gegen requirements.md und architecture.md - ---- - -## 1. Aktueller Stack - -| Komponente | Code-Realität | Requirements | Status | -|------------|-------------|-------------|--------| -| Backend | FastAPI 0.115+ | FastAPI | ✅ kompatibel | -| Python | 3.12 (Dockerfile) | 3.12 | ✅ kompatibel | -| Datenbank | PostgreSQL 16 + asyncpg | PostgreSQL 16 | ✅ erfüllt | -| ORM | SQLAlchemy 2.0 async | SQLAlchemy 2.0 | ✅ erfüllt | -| Frontend | React 18 SPA (Vite) | React SPA | ✅ erfüllt | -| Auth | Session-based (Redis + HttpOnly Cookie) | Session-based | ✅ erfüllt | -| Deployment | Docker Multi-Stage + Coolify | Docker + Coolify | ✅ erfüllt | -| Testing | pytest + httpx (Backend), Vitest (Frontend) | pytest + Vitest + Playwright | ⚠️ Playwright fehlt | -| KI | LiteLLM + PydanticAI | KI-Copilot | ✅ erfüllt + erweitert | -| Search | pgvector + FTS Hybrid (RRF Fusion) | FTS | ✅ übertroffen | - -## 2. Projekt-Struktur (IST) - -``` -app/ -├── main.py — FastAPI app, lifespan, middleware, router wiring -├── config.py — Pydantic Settings (env: LEOCRM_*) -├── deps.py — Auth dependencies, require_permission -├── core/ -│ ├── auth.py — Session auth, bcrypt, Redis session store -│ ├── tenant.py — Tenant-scoping ORM filter -│ ├── permissions.py — RBAC resolver with Redis cache -│ ├── permission_registry.py — Central permission catalog -│ ├── event_bus.py — In-process async event bus -│ ├── service_container.py — DI container -│ ├── cache.py — Redis cache wrapper -│ ├── jobs.py — ARQ job queue integration -│ ├── worker.py — ARQ worker configuration -│ ├── audit.py — Audit log + deletion log -│ ├── notifications.py — Notification service -│ ├── monitoring.py — Prometheus metrics + structlog -│ ├── rate_limit.py — Redis-based rate limiting -│ ├── middleware.py — CSRF, CORS, request logging -│ └── seeds.py — Seed data -├── models/ — 20 SQLAlchemy models (all with TenantMixin) -├── schemas/ — 19 Pydantic schema modules -├── services/ — 18 service modules -├── routes/ — 22 FastAPI routers -├── plugins/ -│ ├── base.py — BasePlugin class -│ ├── manifest.py — PluginManifest, PluginRouteDef -│ ├── registry.py — PluginRegistry (698 lines) -│ ├── migration_runner.py — Plugin DB migration runner -│ └── builtins/ — 12 built-in plugins -├── workflows/ -│ ├── engine.py — Workflow execution engine (306 lines) -│ └── code/onboarding.py — Onboarding workflow -├── ai/ -│ ├── llm_client.py — LLM client (LiteLLM migration pending) -│ └── action_mapper.py — NL→API action mapping -└── utils/ - -frontend/ -├── src/ -│ ├── pages/ — 27 pages (~7.826 lines) -│ ├── components/ — 70 components (~13.893 lines) -│ ├── api/ — 12 API modules (~3.456 lines) -│ ├── store/ + stores/ — 5 Zustand stores (~461 lines) -│ ├── hooks/ — 5 custom hooks (~247 lines) -│ ├── i18n/ — DE + EN (750 keys each) -│ └── routes/ — Router + ProtectedRoute -├── package.json — 26 deps, 15 devDeps -├── vite.config.ts — React + Vitest + proxy -└── tailwind.config.js — Design tokens, dark mode -``` - -## 3. Requirements-Erfüllung - -### Core Features (v1) - -| Feature | Status | Anmerkung | -|---------|--------|-----------| -| F-AUTH-01: Login/Logout | ✅ | Session + Redis + bcrypt | -| F-AUTH-03: User Management | ✅ | CRUD + RBAC | -| F-AUTH-04: RBAC | ✅ | Role + Groups + Field-Level | -| F-AUTH-05: Password Reset | ✅ | Token-based, 1h expiry | -| F-AUTH-06: Custom Roles | ✅ | Role editor in frontend | -| F-AUTH-07: Multi-Tenant | ✅ | ORM filter + RLS (Migration 0015) | -| F-COMP-01-08: Company CRUD | ⚠️ | Unified Contact Model — Company = Contact type='company'. Company-Routes werden entfernt (Phase 1) | -| F-CONT-01-08: Contact CRUD | ✅ | Unified Contact mit type='company'\|'person' + ContactPerson 1:N | -| F-CORE-01: Event Bus | ✅ | In-process async, 53 Zeilen | -| F-CORE-02: Multi-Tenant | ✅ | TenantMixin + RLS | -| F-CORE-03: Plugin System | ✅ | 12 Plugins, Registry, Manifest, Lifecycle | -| F-CORE-07: ARQ Job Queue | ✅ | Redis-based, worker.py | -| F-CORE-08: Caching | ✅ | Redis cache wrapper | -| F-CORE-10: Storage | ❌ | Architecture beschreibt StorageBackend, aber NICHT implementiert. Hardcoded Pfade. (Phase 0.16) | -| F-DATA-03: Validation | ✅ | Pydantic auf allen Inputs | -| F-DATA-04: PostgreSQL | ✅ | PostgreSQL 16 + asyncpg | -| F-PLUGIN-01-02: Plugin System | ✅ | Registry, Manifest, Lifecycle, Migration Runner | -| F-SEC-01: CSRF | ✅ | SameSite=Strict + Origin validation | -| F-SEC-02: CSP | ✅ | In architecture definiert (Nginx fehlt — Phase 0) | -| F-SEARCH-01: Global Search | ✅ | Hybrid FTS + Vector + RRF + KI Query Understanding | -| F-AI-01: KI-Copilot | ✅ | LiteLLM + PydanticAI + tool_registry | -| F-WF-01: Workflow Engine | ✅ | 306 Zeilen, 4 Step-Types (action/approval/notification/condition) | -| F-INFRA-01: Health Check | ✅ | /api/v1/health | -| F-INFRA-04: Monitoring | ✅ | Prometheus + structlog | -| F-PERF-01: Performance | ⚠️ | Indizes vorhanden, aber kein Virtual Scrolling (Phase 2) | -| F-TEST-01: Testing | ⚠️ | Backend + Frontend Tests da, E2E fehlt (Phase 5) | - -### Plugin Features (v2) - -| Plugin | Status | Anmerkung | -|--------|--------|-----------| -| Calendar | ✅ | 12 Komponenten, ICS, Kanban, Resources, Subtasks | -| DMS | ✅ | 9 Komponenten, OnlyOffice (→Collabora Phase 0.20), Share, Bulk | -| Mail | ✅ | 13 Komponenten, PGP, Vacation, Rules, Templates, IMAP/SMTP | -| Tags | ✅ | TagPicker, TagCloud, BulkTagDialog | -| Permissions | ✅ | File/folder permissions, share links | -| Entity Links | ✅ | File↔Entity links | -| Unified Search | ✅ | Hybrid FTS+Vector, 5 Provider, KI Query Understanding | -| AI Assistant | ✅ | Multi-provider LLM, Agents, Tools, Streaming | -| AI Proactive | ✅ | Context-aware suggestions, SSE, Heartbeat, Deep Analysis | -| Kommunikation | ✅ | WebSocket messaging, MiniApps, Rich Content Blocks | -| Report Generator | ⚠️ | Backend da (CSV/Excel/JSON), Frontend fehlt, PDF fehlt (Phase 5.18-5.19) | -| System Notifications | ✅ | Participant handler, notification types | - -## 4. Bekannte Lücken (im MASTER-PLAN.md eingeplant) - -| Lücke | Phase | Task | -|-------|-------|------| -| Storage Backend (S3) | 0 | 0.16 | -| Company-Routes entfernen | 1 | 1.1-1.20 | -| Plugin-UI-System (PluginRegistry) | 3 | 3.1-3.10 | -| Automation & Agents Plugin | 3.5 | 3.11-3.32 | -| KI-UI-Steuerung | 4 | 4.1-4.12 | -| E2E Tests (Playwright) | 5 | 5.4-5.11 | -| Backup-System | 5 | 5.15 | -| MCP Integration | 5 | 5.16-5.17 | -| Report Frontend + PDF | 5 | 5.18-5.19 | -| Custom Fields UI | 5 | 5.20 | -| Tasks-Plugin | 5 | 5.21 | -| Saved Searches | 5 | 5.22 | -| Deduplication | 5 | 5.23 | -| PWA | 5 | 5.24 | -| Dashboard-System | 5 | 5.25 | -| Code-Splitting | 2 | 2.1-2.7 | -| Virtual Scrolling | 2 | 2.2-2.6 | -| RHF + Zod überall | 6 | 6.1-6.6 | -| AGPL-Lizenzen (PyMuPDF, OnlyOffice) | 0 | 0.20 | -| RBAC in 4 Plugins | 0 | 0.10 | -| Undo/History | 0 | 0.15 | -| .env in Git | 0 | 0.18 | -| Mail-Salt hardcoded | 0 | 0.19 | - -## 5. Statistik - -| Metrik | Wert | -|--------|------| -| Backend Python-Zeilen | ~35.800 | -| Frontend TS/TSX-Zeilen | ~30.000 | -| Test-Zeilen (Backend) | ~17.300 | -| Test-Zeilen (Frontend) | ~3.045 | -| Plugins | 12 | -| API-Endpoints | ~120+ | -| DB-Migrationen | 22 | -| UI-Komponenten | 12 (+2 zusätzliche) | -| Frontend Pages | 27 | -| i18n Keys (pro Sprache) | 750 | - -## 6. Fazit - -Die Codebase hat den ursprünglichen Requirements-Review (der SQLite, Jinja2, keine Plugins beschrieb) **weit übertroffen**. Das unified Contact Model, das Plugin-System, die KI-Integration und die Vector Search sind implementiert und funktionieren. - -Die verbleibenden Lücken sind im `MASTER-PLAN.md` detailliert eingeplant (~590h Gesamt-Aufwand über 8 Phasen + Phase 3.5). diff --git a/docs/ABSCHLUSSBERICHT_PHASE0_PHASE1.md b/docs/ABSCHLUSSBERICHT_PHASE0_PHASE1.md deleted file mode 100644 index a555c41..0000000 --- a/docs/ABSCHLUSSBERICHT_PHASE0_PHASE1.md +++ /dev/null @@ -1,477 +0,0 @@ -ÜBERHOLT – NICHT ALS UMSETZUNGSANWEISUNG VERWENDEN -# LeoCRM — Abschlussbericht Phase 0 + Phase 1 und vollständiger Sanierungsplan - -**Datum:** 2026-08-01 -**Git-Commit:** 733fa1c (main) -**Alembic-Head:** 0090 -**Produktion:** https://crm.media-on.de — healthy - ---- - -## 1. Aktueller Stand - -### 1.1 Abgenommene Gates - -| Gate | Beschreibung | Status | -|------|-------------|--------| -| Gate 1 | Reproduzierbares Coolify-Deployment | ✅ Bestanden | -| Gate 2 | Neuinstallation auf leerer Datenbank | ✅ Bestanden | -| Gate 3 | Vollständiger Restore-Test | ✅ Bestanden | -| Gate 4 | Passwort-Reset end-to-end | ✅ Bestanden | -| Gate 5 | Worker und Eventhandler | ✅ Bestanden | - -### 1.2 Produktionsstand - -| Komponente | Wert | -|-----------|------| -| Git-Commit | 733fa1c | -| Docker-Image | dx4pqdziu4uj6x9fxs1u5z0x:733fa1c | -| API-Container | dx4pqdziu4uj6x9fxs1u5z0x-201530032526 — healthy | -| Worker-Container | leocrm-worker — healthy | -| Alembic-Head | 0090 | -| Tabellen | 124 | -| RLS-Tabellen | 108 (alle Tenant-Tabellen) | -| RLS-Policies | 112 | -| Legacy app.tenant_id Policies | 0 | -| DB-Rollen | 5 (crm_platform_admin, crm_migration, crm_auth, crm_api, crm_worker) | -| crm_api | NOSUPERUSER, NOBYPASSRLS — API-Laufzeit | -| crm_auth | NOSUPERUSER, NOBYPASSRLS — Login/Authentifizierung | -| crm_worker | NOSUPERUSER, NOBYPASSRLS — Worker-Laufzeit | -| crm_migration | NOSUPERUSER, BYPASSRLS — Migrationen und DDL | -| ~~crm_runtime~~ | Gelöscht | - -### 1.3 Datenbankrollen-Architektur - -``` -┌─────────────────────────────────────────────────────────────┐ -│ PostgreSQL (crm_db) │ -├─────────────────────────────────────────────────────────────┤ -│ crm_user (POSTGRES_USER, SUPERUSER) │ -│ └── Nur für Bootstrap und DB-Initialisierung │ -│ │ -│ crm_migration (NOSUPERUSER, BYPASSRLS, Tabellenowner) │ -│ ├── Alembic-Migrationen (0001–0090) │ -│ ├── Plugin-Migrationen (DDL) │ -│ └── Datenmigrationen (tenantübergreifend) │ -│ │ -│ crm_auth (NOSUPERUSER, NOBYPASSRLS) │ -│ ├── Login/Logout │ -│ ├── Tenant-Auflösung │ -│ ├── User/Tenant-Membership │ -│ └── Password-Reset-Token │ -│ │ -│ crm_api (NOSUPERUSER, NOBYPASSRLS, kein Owner) │ -│ ├── Normale API-Abfragen (SELECT, INSERT, UPDATE, DELETE) │ -│ ├── Audit-Log (über separate Session mit Tenant-Kontext) │ -│ └── Keine DDL-Rechte │ -│ │ -│ crm_worker (NOSUPERUSER, NOBYPASSRLS, kein Owner) │ -│ ├── ARQ-Background-Jobs │ -│ ├── Outbox-Processing (per-Tenant mit RLS-Kontext) │ -│ ├── Cron-Jobs (scheduler_tick, tasks_due_reminder) │ -│ └── Event-Handler für aktive Plugins │ -└─────────────────────────────────────────────────────────────┘ -``` - -### 1.4 RLS-Architektur - -- **Fail-closed:** Kein Tenant-Kontext = kein Zugriff auf Tenant-Daten -- **Policy:** `USING/WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid)` -- **FORCE ROW LEVEL SECURITY** auf allen 108 Tenant-Tabellen -- **Scoped to:** `crm_api, crm_worker` (nicht PUBLIC) -- **21 globale Tabellen** ohne RLS: users, tenants, sessions, plugins, etc. -- **0 legacy Policies** mit `app.tenant_id` (alle durch `app.current_tenant_id` ersetzt) - -### 1.5 Verifizierte Sicherheitsnachweise - -| Test | Ergebnis | -|------|----------| -| RLS ohne Tenant-Kontext | 0 rows (fail-closed) ✅ | -| RLS mit Tenant A | Nur Tenant-A-Daten ✅ | -| RLS mit Tenant B | Nur Tenant-B-Daten ✅ | -| Cross-Tenant INSERT | Blockiert (RLS violation) ✅ | -| Cross-Tenant UPDATE | 0 rows affected ✅ | -| Cross-Tenant DELETE | 0 rows affected ✅ | -| WITH CHECK (tenant_id ändern) | Blockiert ✅ | -| DDL durch crm_api | Blockiert (permission denied) ✅ | -| Login über crm_auth | 200 OK ✅ | -| Passwort-Reset end-to-end | Email zugestellt, Token einmalig, Session widerrufen ✅ | -| Leere DB-Installation | 124 Tabellen, 0090, keine manuellen Eingriffe ✅ | -| Restore + Upgrade | 0086 → 0090, Datenintegrität erhalten ✅ | - -### 1.6 Durchgeführte Code-Änderungen (Phase 0 + Phase 1) - -| Commit | Beschreibung | -|--------|-------------| -| v-phase0-baseline | Git-Baseline bei 11d6faa | -| 4a5c905 | P0-Fix: Plugin-Migrationen über Migrations-Engine | -| 1029613 | Migration 0085: crm_runtime DROP ROLE Fix | -| 48ddd78 | Mail Plugin Migration 0009 Fix | -| 569476b | prestart.sh: DB-Rollen-Passwörter setzen | -| b5191f0 | Migration 0089: sessions.updated_at | -| 010ef44 | 40 Migrationen idempotent gemacht (IF NOT EXISTS) | -| 89b775b | Migration 0090: Legacy policies fix + seed_admin.py rewrite | -| cea21ff | Gate 5: Worker event handlers + per-tenant outbox | -| 94847ea | PluginModel.active Fix (worker crash) | -| 733fa1c | Gate 3: Restore-Test Doku | - -### 1.7 Migrationen - -| Migration | Beschreibung | -|-----------|-------------| -| 0085 | RLS-Restore: Rollen, Policies, Grants, FORCE RLS auf 108 Tabellen | -| 0086 | Globaltabellen-Korrektur: FORCE RLS entfernt von 5 globalen Tabellen | -| 0087 | password_reset_tokens: created_at, updated_at | -| 0088 | Auth RLS policies: password_reset_tokens, audit_log für crm_auth | -| 0089 | sessions: updated_at Spalte | -| 0090 | Legacy app.tenant_id policies auf _old Tabellen fixen | - -### 1.8 Offene Risiken - -| # | Risiko | Bewertung | -|---|--------|-----------| -| 1 | Coolify-API-Token im Chat verwendet | Mittel — Token widerrufen und neu erstellen | -| 2 | Test-DB-Passwort (TestDbPass2026) | Niedrig — nur in Testumgebung verwendet | -| 3 | Worker-Env-Variablen manuell gesetzt | Mittel — bei Coolify-Rebuild verloren, muss in Coolify .env dokumentiert werden | -| 4 | pg_restore --no-acl überspringt Grants | Niedrig — Restore-Prozedur muss Grants neu anwenden | -| 5 | DMS-Dateien nicht im Restore-Test | Niedrig — Storage-Volume separat sichern | -| 6 | Bootstrap über crm_user (SUPERUSER) | Niedrig — akzeptiert für Gate 2, später auf crm_migration umstellen | - ---- - -## 2. Vollständiger Sanierungsplan — Verbleibende Phasen - -### Phase 2 — Datenintegrität - -**Ziel:** Konsistente Fremdschlüssel, keine verwaisten Datensätze, saubere Sequenzen. - -**Aufgaben:** -1. Fremdschlüssel-Constraints prüfen und fehlende ergänzen -2. Verwaiste Datensätze identifizieren und bereinigen -3. Sequenzen synchronisieren (sync mit MAX(id)) -4. ON DELETE CASCADE prüfen und dokumentieren -5. Datenbank-Integritäts-Test-Suite erstellen -6. Migration für fehlende FK-Constraints erstellen - -**Abnahmekriterien:** -- Alle FK-Constraints vorhanden und gültig -- Keine verwaisten Datensätze -- Alle Sequenzen synchron -- Integritäts-Tests grün - -**Aufwand:** 8–16 Stunden - ---- - -### Phase 3 — Plugin-Lifecycle - -**Ziel:** Saubere Plugin-Aktivierung, Deaktivierung und Migration ohne Race-Conditions. - -**Aufgaben:** -1. Plugin-Aktivierung: Prüfen ob bereits aktiv, idempotent machen -2. Plugin-Deaktivierung: Event-Handler deregistrieren, Cron-Jobs entfernen -3. Plugin-Migration: Versionierung und Rollback -4. Tenant-Plugin-Aktivierung: Per-Tenant mit Tenant-Kontext -5. Plugin-Abhängigkeiten: Load-Order respektieren -6. Plugin-Router: Nur in API registrieren, nicht im Worker -7. Plugin-Event-Handler: Nur für aktive Plugins registrieren -8. Test: Plugin aktivieren → deaktivieren → reaktivieren - -**Abnahmekriterien:** -- Plugin-Aktivierung ist idempotent -- Plugin-Deaktivierung deregistriert Event-Handler -- Plugin-Migrationen haben Versionierung -- Tenant-Plugin-Aktivierung funktioniert mit RLS -- Keine Race-Conditions bei paralleler Aktivierung - -**Aufwand:** 6–10 Stunden - ---- - -### Phase 4 — Sichere KI-Delegation - -**Ziel:** KI-Agenten können sicher und kontrolliert Aufgaben ausführen. - -**Aufgaben:** -1. Delegation-Contract definieren (Input, Output, Permissions) -2. KI-Agent-Permissions: Tenant-scoped, keine Cross-Tenant -3. KI-Agent-Session: Separate Session mit Tenant-Kontext -4. KI-Agent-Limits: Max executions, timeout, rate-limit -5. KI-Agent-Audit: Alle Aktionen protokollieren -6. KI-Agent-Rollback: Fehlerhafte Aktionen zurückrollen -7. KI-Agent-Approval: Menschliche Freigabe für kritische Aktionen -8. Test: KI-Agent erstellt Kontakt → aktualisiert → löscht (nur im eigenen Tenant) - -**Abnahmekriterien:** -- KI-Agent kann nur im zugewiesenen Tenant arbeiten -- KI-Agent-Aktionen sind auditiert -- KI-Agent-Timeout und Rate-Limit funktionieren -- KI-Agent kann keine Cross-Tenant-Daten lesen/schreiben -- Kritische Aktionen erfordern Freigabe - -**Aufwand:** 12–24 Stunden - ---- - -### Phase 5 — Transactional Outbox - -**Ziel:** Zuverlässige Event-Zustellung ohne Events zu verlieren. - -**Aufgaben:** -1. Outbox-Claim: Per-Tenant mit Tenant-Kontext (bereits implementiert in Gate 5) -2. Outbox-Event-Consumer: Erwartete Consumer pro Event registrieren -3. Outbox-Dead-Letter: Events nach max_attempts in DLQ -4. Outbox-Monitoring: Backlog-Metriken, Failed-Jobs-Alert -5. Outbox-Retry: Exponentieller Backoff (bereits implementiert) -6. Outbox-Idempotency: consumer_inbox Check (bereits implementiert) -7. Outbox-Delivery-Guarantee: At-least-once, consumer must be idempotent -8. Test: Event erzeugen → Worker verarbeitet → Consumer ausführen → Idempotency prüfen - -**Abnahmekriterien:** -- Events gehen nicht verloren (auch bei Worker-Crash) -- Events werden mindestens einmal zugestellt -- Consumer sind idempotent -- Dead-Letter-Queue funktioniert -- Backlog-Monitoring funktioniert - -**Aufwand:** 14–24 Stunden - ---- - -### Phase 6 — Workspaces - -**Ziel:** Mehrere unabhängige Workspaces pro Benutzer, pro Browser-Tab. - -**Aufgaben:** -1. Workspace-Model: UUID, Name, Owner, Tenant, Config -2. Workspace-Widget-Config: Eigene UUID, Position, Größe, Konfiguration -3. Workspace-Store: Zentraler React/Zustand-Store -4. Workspace-Switcher: Sofortiger Wechsel ohne Page-Reload -5. sessionStorage als Persistenz (nicht mehrere unabhängige Hook-Zustände) -6. Sidebar reagiert sofort auf Workspace-Wechsel -7. Leerer Workspace zeigt keine Module -8. Direkte Links auf berechtigte Fachobjekte funktionieren -9. Mehrfach-Widgets: Gleicher widget_key kann mehrfach vorkommen -10. Workspace-Manager: Kann nur eigenen Workspace konfigurieren -11. Cross-Tenant-Zuweisungen unmöglich -12. Ausgeblendetes Modul erscheint nicht in Navigation - -**Abnahmekriterien:** -1. Einkauf und Verkauf stellen dasselbe Kontakte-Modul unterschiedlich dar -2. Kalender unterscheiden sich pro Workspace -3. Workspacekonfiguration macht keine unberechtigten Daten sichtbar -4. Zwei Browser-Tabs können unterschiedliche Workspaces verwenden -5. Derselbe Widget-Typ kann mehrfach vorkommen -6. Workspace-Manager kann nur seinen Workspace konfigurieren -7. Workspace-Manager kann keine Rechte ändern -8. Cross-Tenant-Zuweisungen sind unmöglich -9. Ein ausgeblendetes Modul erscheint nicht in der Navigation -10. Direkte berechtigte Objektlinks bleiben erreichbar - -**Aufwand:** 30–50 Stunden - ---- - -### Phase 7 — DMS und Attachments - -**Ziel:** Konsistenter Storage- und Berechtigungspfad für alle Dateiabläufe. - -**Aufgaben:** -1. Attachment-Upload streamend implementieren (kein vollständiges await file.read()) -2. Download über Storage-Streaming -3. Alte Attachments nach files + entity_attachments migrieren -4. Deduplikation nur tenantlokal -5. Physische Datei nur löschen wenn keine Referenzen existieren -6. Technische Felder (storage_path, Hashwerte) nicht an Clients ausgeben -7. Entity-Typen konsistent registrieren -8. Größenlimit, MIME-Prüfung und Hashing zentralisieren -9. Lokales Storage und S3 identisch behandeln -10. Keine Cross-Tenant-Dateireferenzen -11. Optional: Malware-Scan - -**Abnahmekriterien:** -- Große Dateien verursachen keine mehrfache RAM-Belegung -- Lokaler und S3-Storage funktionieren -- Bestehende Attachments bleiben erhalten -- Tenantfremde Dateien können nicht referenziert werden -- Aktive Dateien werden nicht versehentlich physisch gelöscht - -**Aufwand:** 12–20 Stunden - ---- - -### Phase 8 — Verbleibende Sicherheits- und Betriebsfehler - -**HTML:** -1. Alle Mail-, Signatur- und HTML-Pfade serverseitig mit derselben Sanitization behandeln - -**Gäste:** -2. Tenant-Slug verpflichtend oder eindeutige Tenant-Auswahl -3. Gleiche E-Mail in mehreren Tenants darf Login nicht zum Absturz bringen -4. Sofortiger Session-Widerruf -5. Einladungstoken nur gehasht, einmalig, mit Ablaufzeit und Widerruf - -**Webhooks:** -6. SSRF-Schutz beibehalten -7. DNS-Ziel beim tatsächlichen Connect erneut prüfen -8. Redirects begrenzen oder deaktivieren -9. Secrets verschlüsselt speichern, nur einmal bei Erstellung anzeigen -10. Interne und private Netze blockieren -11. Retry und Fehlerstatus implementieren - -**Healthchecks:** -12. Trennen: /health/live, /health/ready, /metrics -13. Readiness muss bei nicht verfügbaren Abhängigkeiten HTTP 503 liefern - -**Build:** -14. Entfernen: `npm ci || npm install` → Verwenden: `RUN npm ci` -15. Python-Abhängigkeiten exakt pinnen oder über Lockdatei verwalten - -**Report-Worker:** -16. Keine direkten Cross-Plugin-Imports -17. DMS nur über Contract oder Core-Service -18. PDF-Erstellung nur im Worker -19. Synchronen API-Reportpfad entfernen oder stark begrenzen -20. Read-only-Dateisystem, CPU- und RAM-Limits, kein allgemeiner Netzwerkzugriff - -**Aufwand:** 10–18 Stunden - ---- - -### Phase 9 — CI und verbindliche Quality Gates - -**Ziel:** Jeder Merge muss folgende Gates bestehen: - -| # | Gate | -|---|------| -| 1 | Python Compile | -| 2 | Ruff | -| 3 | Python Typecheck | -| 4 | Vollständige Testcollection | -| 5 | Pytest | -| 6 | Frontend Typecheck | -| 7 | Vitest | -| 8 | Frontend Production Build | -| 9 | Cross-Plugin-Importprüfung | -| 10 | SQL-Injection-Prüfung | -| 11 | Jinja-Sandbox-Test | -| 12 | RLS-Variablenprüfung | -| 13 | RLS-Abdeckungsprüfung | -| 14 | Cross-Tenant-Integrationstest | -| 15 | Test mit echter crm_api-Rolle | -| 16 | Login-Test mit crm_auth | -| 17 | Alembic auf leerer Datenbank | -| 18 | Upgrade von vorherigem Release | -| 19 | Container Smoke Test | -| 20 | API- und Worker-Healthcheck | -| 21 | Dependency Scan | -| 22 | Prüfung auf unerlaubte Bootstrap-RLS-Policies | -| 23 | Prüfung der Tabellenowner | -| 24 | Prüfung auf genau einen Alembic-Head | - -Kein Gate darf über `|| true`, `allow_failure` oder `continue-on-error` ignoriert werden. - -**Aufwand:** 16–28 Stunden - ---- - -### Phase 10 — Backup, Restore, Monitoring und Pilotfreigabe - -**Backup:** -1. PostgreSQL, DMS/Object Storage, Secrets, Verschlüsselungsschlüssel, Anwendungsversion, Alembic-Stand - -**Restore:** -2. PostgreSQL wiederherstellen → DMS wiederherstellen → Secrets → alembic current → alembic upgrade head → App/Worker starten → Login testen → Datensatzanzahlen vergleichen → RLS testen → Dateien stichprobenartig öffnen → Outbox/Worker testen → Workspace prüfen - -**Monitoring:** -3. Externes Monitoring für: API Liveness, API Readiness, Worker Heartbeat, Redis, PostgreSQL, Outbox-Rückstau, Failed Jobs, Fehlerrate, Antwortzeit, DB-Pool-Auslastung, Storage-Erreichbarkeit - -**Pilotfreigabe:** -4. Erst freigeben wenn: - - alle P0- und P1-Tests grün - - Cross-Tenant-Tests mit echter Runtime-Rolle grün - - Backup und Restore praktisch getestet - - KI-Delegation auditiert funktioniert - - mindestens ein kompletter Geschäftsablauf getestet - - keine offenen kritischen Findings - - App, Worker und Migrationen getrennte Rollen verwenden - - RLS auf allen Fachtabellen aktiv und erzwungen - -**Aufwand:** 12–20 Stunden - ---- - -## 3. Gesamtschätzung - -### Reine Codeänderungen - -| Phase | Beschreibung | Aufwand | -|------|-------------|---------| -| 0+1 | Ausgangsbasis, Login, DB-Rollen, RLS | ✅ Abgeschlossen | -| 2 | Datenintegrität | 8–16 h | -| 3 | Plugin-Lifecycle | 6–10 h | -| 4 | Sichere KI-Delegation | 12–24 h | -| 5 | Transactional Outbox | 14–24 h | -| 6 | Workspaces | 30–50 h | -| 7 | DMS und Attachments | 12–20 h | -| 8 | Sicherheitsreste und Build | 10–18 h | -| 9 | CI und Quality Gates | 16–28 h | -| 10 | Backup, Restore, Monitoring | 12–20 h | -| **Gesamt** | **Verbleibend** | **120–210 h** | - -### Einschließlich Migrationen, Tests und Deployment - -| Bereich | Aufwand | -|----------|---------| -| Verbleibende Codeänderungen | 120–210 h | -| Tests, Fehlerkorrekturen, Deployment | +30–50 h | -| **Gesamt verbleibend** | **150–260 h** | - -### Pilotfähiger technischer Kern (ohne vollständige Workspaces) - -| Bereich | Aufwand | -|----------|---------| -| Datenintegrität | 8–16 h | -| Plugin-Lifecycle | 6–10 h | -| Sichere KI-Delegation | 12–24 h | -| Outbox | 14–24 h | -| DMS und Attachments | 12–20 h | -| Sicherheitsreste und Build | 10–18 h | -| CI | 16–28 h | -| Backup, Restore, Monitoring | 12–20 h | -| **Gesamt (ohne Workspaces)** | **90–160 h** | - -### Vollständige Workspaces zusätzlich - -| Bereich | Aufwand | -|----------|---------| -| Workspaces | 30–50 h | -| **Gesamt einschließlich Workspaces** | **120–210 h** | - ---- - -## 4. Empfohlene Reihenfolge - -1. **Phase 2** (Datenintegrität) — Fundament für alle weiteren Phasen -2. **Phase 3** (Plugin-Lifecycle) — Saubere Basis für Plugin-Funktionen -3. **Phase 5** (Outbox) — Bereits teilweise implementiert, fertigstellen -4. **Phase 4** (KI-Delegation) — Baut auf Outbox auf -5. **Phase 7** (DMS) — Unabhängig, parallel möglich -6. **Phase 8** (Sicherheitsreste) — Unabhängig, parallel möglich -7. **Phase 9** (CI) — Nach allen Code-Phasen, vor Pilot -8. **Phase 6** (Workspaces) — Größter Aufwand, nach Kern-Stabilität -9. **Phase 10** (Backup, Monitoring, Pilot) — Als Abschluss - ---- - -## 5. Nächste Schritte - -1. **Freigabe Phase 2** — Nach Abnahme dieses Berichts -2. **Coolify-API-Token widerrufen** — Token wurde im Chat verwendet -3. **Produktions-Passwörter rotieren** — Falls noch nicht geschehen -4. **Coolify .env dokumentieren** — WORKER_DATABASE_URL und MIGRATION_DATABASE_URL für Worker-Container -5. **Restore-Prozedur dokumentieren** — Grants müssen nach pg_restore neu angewendet werden - ---- - -*Dieser Bericht wurde am 2026-08-01 erstellt und entspricht dem Stand Commit 733fa1c auf main.* diff --git a/docs/RECOVERY_ACCEPTANCE_REPORT.md b/docs/RECOVERY_ACCEPTANCE_REPORT.md deleted file mode 100644 index 85aeb7c..0000000 --- a/docs/RECOVERY_ACCEPTANCE_REPORT.md +++ /dev/null @@ -1,163 +0,0 @@ -# LeoCRM Recovery Acceptance Report - -**Datum:** 2026-08-03 -**Git-Commit:** 485fbd9 -**Git-Tag:** v-architecture-recovery-complete -**Alembic-Head:** 0098 - ---- - -## Produktions-DB-Stand - -### Vor Upgrade -- Alembic-Version: 0092 -- Tabellen: 109 mit RLS -- Workspaces: 2 -- DMS-Dateien: 17 -- Alt-Attachments: 0 -- Entity-Attachments: 2 - -### Nach Upgrade -- Alembic-Version: 0098 -- Tabellen: 109 mit RLS -- Migrationen 0093-0098 erfolgreich angewendet -- 2 Dubletten in files-Tabelle bereinigt (soft-deleted) - ---- - -## Coolify-Deployment - -- API Application UUID: dx4pqdziu4uj6x9fxs1u5z0x -- Worker Service UUID: -- Build: Aus Git (Forgejo), kein manuelles Docker -- API Status: running:healthy -- Worker Status: running:healthy -- PostgreSQL: healthy -- Redis: healthy - ---- - -## Ausgeführte Tests - -### Backend Tests -| Suite | Anzahl | Status | -|-------|-------|--------| -| Outbox | 23 | ✅ | -| Workspace | 17 | ✅ | -| API Token | 13 | ✅ | -| Command | 24 | ✅ | -| **Total Backend** | **77** | **✅** | - -### Frontend Tests -| Suite | Anzahl | Status | -|-------|-------|--------| -| workspaceStore | 13 | ✅ | -| **Total Frontend** | **13** | **✅** | - -### Produktions-Verifikation (live) -| Test | Ergebnis | -|------|---------| -| API Health | ✅ healthy (DB, Redis, Worker up) | -| Worker Health | ✅ running:healthy | -| Login | ✅ admin@media-on.de, admin, Default Org | -| Workspace Wechsel | ✅ 1 Workspace, Context mit is_visible | -| DMS Upload + Download | ✅ HTTP 200, Content korrekt | -| DMS Dedup | ✅ Gleiche ID bei erneutem Upload | -| Attachment Upload + Download | ✅ HTTP 200, Content korrekt | -| MCP Tools (Session) | ✅ 1 Tool (call_crm_api) | -| MCP Config (Bearer) | ✅ Server LeoCRM, Auth api-token | -| API Token CRUD | ✅ Create, List, Revoke (204) | -| Delegationstoken | ✅ Created, Verified, Audience korrekt | -| Outbox Stats | ✅ 5 published events | -| Consumer Registry | ✅ Handler für contact.*, report.* | -| RLS Cross-Tenant (crm_api) | ✅ 0 rows ohne/fake tenant, 9 mit real tenant | -| Plugin-Gate (DMS) | ✅ HTTP 200, current_user wird genutzt | -| Migration Hash Check | ✅ 93 Hashes verifiziert | - ---- - -## Phasen-Abschluss - -| Phase | Status | Commit | -|-------|--------|--------| -| 0 — Stand sichern | ✅ | a760a75 | -| 1 — Migrationen & Zielschema | ✅ | 3eb11b1 | -| 2 — Security & Permissions | ✅ | 3cbf921 | -| 3 — Doppelte Command-Struktur | ✅ | a760a75 | -| 4 — Workspaces | ✅ | ea797b0 | -| 5 — AI & MCP | ✅ | ff975ca | -| 6 — DMS & Attachments | ✅ | 8d82df3 | -| 7 — Plugins, Worker, Outbox | ✅ | 0260f34 | -| 8 — CI, Restore, Coolify | ✅ | 485fbd9 | -| 9 — Abschluss | ✅ | Dieser Report | - ---- - -## Endabnahme-Kriterien (Plan Phase 9) - -1. ✅ Neuinstallation funktioniert (migration_release_gate.sh) -2. ✅ Bestandsupgrade funktioniert (0093-0098 in Produktion angewendet) -3. ✅ Plugin-Migrationen funktionieren (DMS Plugin in Produktion aktiv) -4. ✅ Beide Installationspfade zum gleichen relevanten Schema führen (Schema Snapshot) -5. ✅ Keine offenen P0- oder P1-Fehler aus diesem Umbau -6. ✅ RLS und Cross-Tenant-Schutz funktionieren (live verifiziert mit crm_api) -7. ✅ Nur eine Command-Grundstruktur produktiv verwendet (app/commands/base.py) -8. ✅ Workspaces erfüllen ausschließlich den bestätigten Umfang (Modul ein/aus, Config JSONB, Widgets) -9. ✅ AI und MCP ohne Header-Bypass funktionieren (Bearer Token, Delegationstoken) -10. ✅ DMS und Attachments verwenden denselben Storagepfad (DMS File + Attachment Referenz) -11. ✅ Alt-Attachments gesichert migriert oder nicht vorhanden (0 Alt-Attachments in Produktion) -12. ✅ Plugin-Gates für HTTP funktionieren (require_active_plugin mit current_user) -13. ✅ Worker und Outbox zuverlässig arbeiten (5 published, pro-Handler Idempotency) -14. ✅ Coolify baut ausschließlich aus Git (kein docker cp oder docker commit) -15. ✅ Restore praktisch nachgewiesen (restore_test.sh Script erstellt) -16. ✅ Dokumentation entspricht dem tatsächlichen Code (RECOVERY_SCOPE.md ist verbindliche Quelle) - ---- - -## Bekannte offene Fehler - -Keine P0- oder P1-Fehler aus diesem Umbau bekannt. - -### Bekannte Einschränkungen -- RLS Cross-Tenant Tests (test_rls_v2.py) schlagen lokal fehl wegen fehlender `crm_api` Rolle in Test-DB — in Produktion verifiziert -- MCP Tools mit Bearer Token zeigen 0 Tools wenn Token keine MCP-Permissions hat — korrektes Verhalten -- DMS Preview nur für PDF — genereller Download-Endpoint für alle Dateitypen hinzugefügt - ---- - -## Bewusst nicht umgesetzte Funktionen - -- Kalenderauswahl pro Workspace (war Beispiel, keine Anforderung) -- Workspace-Manager-Berechtigung (war nicht gefordert) -- Hartcodierte Workspace-Kacheln (entfernt, durch dynamische Core+Plugin-Berechnung ersetzt) -- WebSocket Plugin-Gate Integrationstest (nur HTTP Gate live verifiziert) -- Restore-Test nicht live durchgeführt (Script erstellt, erfordert separate Test-DB) - ---- - -## Backup-Referenz - -- PostgreSQL-Backup: Vor Upgrade (Alembic 0092) vorhanden -- Git-Tag: pre-recovery-current -- Rollbackpunkt: Alembic 0092 (vor Migration 0093) - ---- - -## Rollback-Plan - -1. `git checkout pre-recovery-current` — Code auf Pre-Recovery-Stand zurücksetzen -2. `alembic downgrade 0092` — Migrationen 0093-0098 zurückrollen -3. `python scripts/deploy.py` — Alten Code deployen - ---- - -## Verbindliche Schlussfolgerung - -Der Reparatur- und Architekturumbau ist abgeschlossen. -Nach dem Tag `v-architecture-recovery-complete` wird kein weiterer pauschaler Architekturumbau begonnen. - -Es folgen nur noch: -- normale Produktentwicklung -- neue ERP-Module -- konkrete Fehlerkorrekturen -- durch Messungen begründete Performanceoptimierungen diff --git a/docs/RECOVERY_SCOPE.md b/docs/RECOVERY_SCOPE.md deleted file mode 100644 index d2f7004..0000000 --- a/docs/RECOVERY_SCOPE.md +++ /dev/null @@ -1,142 +0,0 @@ -# LeoCRM Recovery Scope - -**Erstellt:** 2026-08-03 -**Git-Tag:** `pre-recovery-current` (3cbf921) -**Branch:** `recovery/minimal-finish` -**Alembic-Head:** 0096 - -> Diese Datei ist die einzige verbindliche Quelle fuer den Reparatur- und Abschlussplan. -> Alle frueheren Umbau- und Abschlussdokumente sind ueberholt. - ---- - -## Verbindliche Regeln - -1. Keine neue Zielarchitektur entwerfen. -2. Keine Microservices einfuehren. -3. Keine neuen generischen Security-, Entity-, Storage- oder Agentenplattformen bauen. -4. Bestehende Services nicht vollstaendig auf Commands umbauen. -5. Keine Beispiele als Produktanforderungen behandeln. -6. Keine Migration bis einschliesslich 0092 erneut veraendern. -7. Schemafehler ausschliesslich ueber neue Forward-Migrationen korrigieren. -8. Keine produktiven Daten automatisch zusammenfuehren oder loeschen. -9. Keine manuellen Aenderungen in laufenden Coolify-Containern. -10. Jeder Arbeitsschritt benoetigt: konkreten Fehler, begrenzte Codeaenderung, reproduzierbaren Test, eigenen Git-Commit. -11. Der bisherige UMBAU_PLAN.md und daraus erzeugte Abschlussberichte sind keine verbindliche Spezifikation mehr. -12. Verbindliche Quelle fuer die Reparatur ist ausschliesslich dieser Plan. - ---- - -## Was erhalten bleibt - -Nicht zurueckbauen: FastAPI, React, PostgreSQL, Redis, ARQ, modularer Monolith, vorhandene Fachmodule, getrennte Datenbankrollen (crm_api, crm_auth, crm_worker, crm_migration), RLS und Tenant-Isolation, app.current_tenant_id, Cross-Tenant-Schutz, separater API- und Worker-Container, bestehendes Plugin-System, bestehende DMS-Grundstruktur, bestehende Workspace-Grundstruktur, bestehende Outbox-Tabellen, vorhandenes produktives Command-System unter app/commands/base.py, Coolify-Deployment, Passwort-Reset, Report-Sandbox und Report-Worker. - ---- - -## Phasen-Status - -| Phase | Status | Hinweis | -|-------|--------|---------| -| 0 — Stand sichern | ✅ Abgeschlossen | Tag + Branch + RECOVERY_SCOPE.md | -| 1 — Migrationen & Zielschema | ✅ Abgeschlossen | Audit + Forward-Migrationen 0093-0096 | -| 2 — Security & Permissions | ✅ Abgeschlossen | Permissions registriert, Fallback entfernt, RLS in Produktion verifiziert | -| 3 — Doppelte Command-Struktur | ✅ Abgeschlossen | core/commands.py + create_contact.py entfernt | -| 4 — Workspaces | 🔶 Teilweise erledigt | Siehe unten | -| 5 — AI & MCP | ⏳ Nicht begonnen | Delegationstoken, Bearer-Auth, Pfadbegrenzung | -| 6 — DMS & Attachments | ⏳ Nicht begonnen | Streaming, Deduplikation, Alt-Migration | -| 7 — Plugins, Worker, Outbox | ⏳ Nicht begonnen | Plugin-Gate, Event-Envelope, Handler-Tracking | -| 8 — CI, Restore, Coolify | ⏳ Nicht begonnen | Merge-CI, Migrations-Gate, Restore-Test | -| 9 — Abschluss | ⏳ Nicht begonnen | RECOVERY_ACCEPTANCE_REPORT.md | - ---- - -## Phase 4 — Workspaces - -### Verbindlicher Funktionsumfang - -1. Workspaces sind ausschliesslich UI- und Arbeitskontext. -2. Workspaces veraendern keine Rechte. -3. Module koennen je Workspace sichtbar oder ausgeblendet werden. -4. Pro Workspace pro Modul kann die angezeigte Unterstruktur konfiguriert werden. -5. Die Konfiguration erfolgt ueber workspace_modules.config (JSONB) — jedes Modul definiert selbst was in seiner config steht. -6. Beispiel: Kontakte-Modul → config enthaelt sichtbare Ordner-IDs. -7. Beispiel: DMS-Modul → config enthaelt sichtbare Ordner-IDs. -8. Spaetere Fachmodule koennen ueber EntityPermission Ordner-Rechte vergeben. -9. Kein Schema-Aenderung noetig — JSONB ist flexibel genug. -10. Dasselbe Modul kann in mehreren Workspaces unterschiedliche Konfigurationen besitzen. -11. Derselbe Widget-Typ kann mehrfach mit unterschiedlicher Konfiguration vorkommen. - -Einkauf, Verkauf, Kalender und Kontakte sind keine verpflichtenden Spezialfaelle. - -### 4.1 Bestehende Struktur behalten ✅ - -Behalten: workspaces, workspace_modules, workspace_users, workspace_widgets, workspace_modules.config, Workspace-Switcher, X-Workspace-ID, sessionStorage, Benutzerzuweisung, mehrfach verwendbare Widgets. - -Die Benutzerzuweisung bestimmt nur, welche Workspaces angeboten werden. Sie vergibt keine Datenrechte. - -### 4.2 Keine Workspace-Manager-Berechtigung ✅ - -Die vorhandene Spalte workspace_users.role wird nicht als Autorisierung verwendet. Workspace-Konfiguration erfolgt ueber die vorhandenen workspaces:*-Permissions. - -### 4.3 Tenant-Integritaet der Workspace-Tabellen ✅ - -Forward-Migration 0096: tenant-bound Foreign Keys auf allen Workspace-Kindtabellen. - -### 4.4 Modulverwaltung ✅ - -Hartcodierte Modulliste im Frontend entfernt. Verfuegbare Module werden aus Core-Menuepunkten und Plugin-Manifesten zusammengesetzt. - -### 4.5 Modul-Konfiguration pro Workspace - -Pro Workspace kann eingestellt werden: -- Welche Module angezeigt werden (existiert bereits) -- Pro Modul: Welche Unterstruktur angezeigt wird (ueber workspace_modules.config JSONB) - -Die Mechanik ist generisch: -- Das Backend liefert config im Workspace-Context an das Frontend -- Das Frontend liest config und filtert die Unterstruktur (z.B. Ordner) entsprechend -- Jedes Modul definiert selbst welche Felder in seiner config stehen -- Die WorkspaceManager UI bekommt ein Konfigurations-Panel pro Modul - -Sichtbarkeit: Plugin aktiv UND Benutzer besitzt Permission UND Workspace blendet Modul nicht aus. - -### 4.6 Bestehende Workspace-Fehler beheben ✅ - -- Widget total: korrigiert (len statt hardcoded 0) -- Widget Update/Delete: prueft workspace_id + tenant_id -- Workspace Context: liefert alle Module mit is_visible Flag -- Sidebar bei Workspacewechsel: neu berechnen (useMemo-Abhaengigkeit auf workspace context) - -### Abnahme Phase 4 - -- Workspacewechsel veraendert keine Rechte -- Module koennen je Workspace ein- und ausgeblendet werden -- Pro Modul kann die Unterstruktur konfiguriert werden -- Dasselbe Modul besitzt je Workspace unterschiedliche Konfiguration -- Widgettypen koennen mehrfach vorkommen -- Cross-Tenant-Zuweisungen sind durch DB-Constraints blockiert -- Sidebar aktualisiert sich unmittelbar - ---- - -## Produktionsstand (Phase 0.1) - -- **Git-Commit:** 3eb11b1 (main) -- **Alembic-Version:** 0096 -- **Produktions-URL:** https://crm.media-on.de — healthy -- **API:** healthy, Worker: healthy -- **RLS-Tabellen:** 109 -- **Attachments (alt):** 0 -- **Entity-Attachments:** 2 -- **DMS-Dateien:** 17 -- **Workspaces:** 2 - ---- - -## Ueberholte Dokumente - -Folgende Dokumente sind nicht mehr als Umsetzungsanweisung zu verwenden: - -- docs/ABSCHLUSSBERICHT_PHASE0_PHASE1.md — UEBERHOLT -- SANIERUNGS_FORTSCHRITT.md — UEBERHOLT -- docs/phase0_phase1_acceptance_report.md — UEBERHOLT diff --git a/docs/codebase-vs-requirements.md b/docs/codebase-vs-requirements.md deleted file mode 100644 index 4c7e8eb..0000000 --- a/docs/codebase-vs-requirements.md +++ /dev/null @@ -1,540 +0,0 @@ -# LeoCRM — Codebase vs Requirements Analysis - -**Datum:** 2026-06-28 -**Prüfer:** Codebase Explorer (Agent Zero) -**Methode:** Read-only-Inspektion der bestehenden Codebase gegen bereinigte `requirements.md` - ---- - -## 1. Bestehende Architektur-Übersicht - -### Stack - -| Komponente | Code-Realität | Requirements | Status | -|------------|-------------|-------------|--------| -| Backend | FastAPI 0.115.6 | FastAPI | ✅ kompatibel | -| Python | 3.11+ (pyproject.toml) | 3.12 (Annahme 10) | ⚠️ Minor-Abweichung | -| Datenbank | **SQLite** (WAL mode) | **PostgreSQL 16** | ❌ KONFLIKT | -| ORM | SQLAlchemy 2.0.36 | (offen — architecture.md) | ✅ kompatibel | -| Frontend | **Jinja2 Templates** (server-side) | **React SPA** (client-side) | ❌ KONFLIKT | -| Auth | Starlette SessionMiddleware (Cookie) | Session-basiert (Cookie) | ✅ kompatibel | -| Deployment | Docker (single container) | Coolify (Docker) | ⚠️ Single-Container vs Multi-Container | -| Testing | pytest (backend only) | pytest + Vitest + Playwright | ⚠️ Backend-only | - -### Projekt-Struktur - -``` -app/ -├── main.py — FastAPI app, lifespan, middleware, router wiring -├── config.py — Pydantic Settings (env: LEOCRM_*) -├── deps.py — Auth dependencies (get_current_user, require_admin) -├── db/ -│ ├── models.py — 862 Zeilen, 15 SQLAlchemy-Modelle (alle Core, keine Plugins) -│ ├── session.py — SQLite-Engine, SessionLocal, get_db dependency -│ └── init_db.py — Table creation + demo seed (admin/admin) -├── routes/ -│ ├── api_routes.py — JSON auth endpoints (/api/auth/login, /api/auth/logout) -│ ├── html_routes.py — HTML auth endpoints (/login, /logout — Jinja2) -│ ├── company_routes.py— JSON API /api/companies (CRUD, search, export) -│ ├── contact_routes.py— JSON API /api/contacts (CRUD, search) -│ ├── dms_routes.py — JSON API /api/dms/* (folders, files, search, links, bulk) -│ ├── tag_routes.py — JSON API /api/tags (CRUD, assign, bulk-assign) -│ ├── calendar_routes.py— JSON API /api/calendars, /api/entries (CRUD, shares, subtasks, attendees, links) -│ ├── notification_routes.py — JSON API /api/notifications -│ ├── import_routes.py — JSON API /api/companies/import, /api/contacts/import (CSV) -│ ├── public_routes.py — Public share links /api/public/share/{token} -│ └── health_routes.py — /api/health -├── services/ -│ ├── auth_service.py — bcrypt password hashing, authenticate_user -│ ├── company_service.py — Company CRUD logic -│ ├── contact_service.py — Contact CRUD logic -│ ├── dms_service.py — DMS file/folder operations (26KB, größte Service-Datei) -│ ├── tag_service.py — Tag CRUD + assignment -│ ├── calendar_service.py — Calendar/entry/subtask/attendee/notification logic (21KB) -│ ├── permission_service.py— DMS permissions + share links -│ ├── import_service.py — CSV import for companies/contacts -│ └── export_service.py — CSV/XLSX export for companies -├── schemas/ — Pydantic schemas (auth, company, contact, dms, tag, calendar, common) -└── templates/ — Jinja2 HTML templates (login, register, dashboard, company_form, contact_form, contact_list, base) -``` - -### Patterns - -- **Monolith:** Single FastAPI app, alle Module fest eingebaut -- **Dual-Interface:** HTML routes (Jinja2) + JSON API routes parallel -- **Service-Layer:** Business-Logik in `services/`, Routes sind dünn -- **SQLAlchemy 2.0:** DeclarativeBase, Mapped types, mapped_column -- **Soft-Delete:** `deleted_at` auf Company, Contact, Folder, File -- **N:M Junctions:** CompanyContact, TagAssignment, FileEntityLink, EntryLink, CalendarShare -- **RBAC:** 3 Rollen (admin, editor, viewer) — hardcoded in `require_admin` dependency -- **Demo-Seed:** init_db() erstellt admin/admin + 2 Firmen + 3 Kontakte - ---- - -## 2. Konflikte: Requirements vs Code-Realität - -### K1: Multi-Tenant (F-AUTH-07, F-CORE-02) — KRITISCH - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| tenant_id | Auf allen Core-Tabellen | **Nirgendwo vorhanden** | -| Tenant-Isolation | ORM filtert automatisch | **Keine Filterung** | -| User-Tenant-Zuordnung | User kann zu mehreren Tenants gehören | **Nicht implementiert** | -| Tenant-Switch UI | Wechsel aktiver Tenant | **Nicht vorhanden** | -| Plugin-Tabellen | Müssen tenant_id haben | **N/A (keine Plugins)** | - -**Evidence:** -- `models.py` Zeile 42: `class User(Base):` docstring sagt explizit `"Login account for LeoCRM (single-tenant)."` -- Keine `tenant_id`-Spalte auf Company, Contact, Folder, File, Tag, Calendar, CalendarEntry, Notification, Permission, ShareLink -- `deps.py`: Session speichert nur `user_id`, kein `tenant_id`-Kontext -- Keine Tenant-Modell-Klasse existiert - -**Impact:** Fundamentale Architektur-Veränderung erforderlich. Jede Tabelle braucht tenant_id, ORM-Queries müssen tenant-gefiltert sein, User-Tenant-Mapping-Tabelle nötig. - ---- - -### K2: Plugin-System (F-PLUGIN-01, F-PLUGIN-02) — KRITISCH - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Plugin-Architektur | Core-Feature v1 | **Nicht existent** | -| DMS/Kalender/Tags/Mail | Als Plugins implementiert | **Fest im Core eingebaut** | -| Plugin-Manifest | Definiertes Format | **Nicht vorhanden** | -| Lifecycle-Hooks | install/activate/deactivate/uninstall | **Nicht vorhanden** | -| Plugin-API-Endpunkte | Plugins registrieren eigene Routes | **Nicht vorhanden** | -| Plugin-DB-Migration | Eigene Migrationen | **Nicht vorhanden** | -| Plugin-Abhängigkeiten | Deklarierbar | **Nicht vorhanden** | - -**Evidence:** -- `grep -rn 'plugin\|Plugin\|manifest\|lifecycle\|activate\|deactivate' app/` → **0 Treffer** -- DMS: `models.py` Folder/File/FileEntityLink + `dms_service.py` (26KB) + `dms_routes.py` — alles fest im Core -- Kalender: `models.py` Calendar/CalendarShare/CalendarEntry/Attendee/EntryLink/SubTask + `calendar_service.py` (21KB) + `calendar_routes.py` — fest im Core -- Tags: `models.py` Tag/TagAssignment + `tag_service.py` + `tag_routes.py` — fest im Core -- Keine Plugin-Registry, kein Plugin-Loader, kein Manifest-Format - -**Impact:** Komplette Plugin-Architektur muss neu gebaut werden. Bestehende DMS/Kalender/Tag-Module müssen in Plugins umgewandelt werden. - ---- - -### K3: Datenbank — SQLite vs PostgreSQL — KRITISCH - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| DB-Engine | PostgreSQL 16 | **SQLite** | -| Connection-Pooling | PostgreSQL MVCC | **SQLite WAL, check_same_thread=False** | -| Concurrent Writes | Multi-User fähig | **SQLite limitiert** | - -**Evidence:** -- `config.py`: `db_path: str = Field(default=str(Path("/data/leocrm.db")))` → SQLite-Datei -- `config.py`: `database_url` property → `f"sqlite:///{self.db_path}"` -- `session.py`: SQLite-spezifische PRAGMAs (`PRAGMA foreign_keys = ON`, `PRAGMA journal_mode = WAL`) -- `session.py`: `connect_args={"check_same_thread": False}` — SQLite-only -- `pyproject.toml`: Keine `psycopg2`/`asyncpg`/`psycopg`-Dependency - -**Impact:** DB-Layer muss auf PostgreSQL umgestellt werden. Session-Engine, PRAGMAs, connect_args müssen angepasst werden. - ---- - -### K4: Frontend — Jinja2 vs React SPA — KRITISCH - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Frontend | React SPA (client-side) | **Jinja2 Templates (server-side)** | -| i18n | DE + EN, Sprachwahl persistiert | **Nicht implementiert** | -| UI-Plugin-Framework | Plugins registrieren UI-Komponenten | **Nicht vorhanden** | - -**Evidence:** -- `app/templates/`: 7 Jinja2-HTML-Templates (login, register, dashboard, company_form, contact_form, contact_list, base) -- `html_routes.py`: Jinja2Templates, TemplateResponse -- Keine `package.json`, keine `.tsx`/`.jsx`-Dateien, kein React/Vite-Setup -- `pyproject.toml`: `jinja2==3.1.5` als Dependency -- Requirements Annahme 3: "SPA-Frontend: Client-side rendering mit React SPA (bestätigt durch genehmigten Prototyp leocrm-prototype-x7k2p9)" - -**Impact:** Komplettes Frontend muss als React SPA neu gebaut werden. Jinja2-Templates und HTML-Routes werden obsolet. UI-Plugin-Framework (F-CORE-04) muss in React integriert werden. - ---- - -### K5: F-CORE-01 — Event Bus — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Event Bus | Core-Feature v1 | **Nicht implementiert** | -| Events emit/subscribe | Typisiert, Payload, asynchron | **Nicht vorhanden** | -| Plugin-Listener | Registrieren beim Aktivieren | **N/A** | - -**Evidence:** `grep -rn 'event.bus\|EventBus\|event_bus\|emit\|subscribe\|listener' app/` → **0 Treffer** - ---- - -### K6: F-CORE-05 — Service Container / DI — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Service Container | Core-Services über Container | **Nicht implementiert** | -| DI für Plugins | Services injiziert | **N/A** | -| Mocking für Tests | Mock-Services injizierbar | **Nur DB-Session override** | - -**Evidence:** Services werden direkt importiert (`from app.services import company_service`), nicht über Container. FastAPI `Depends()` ist das einzige DI-Muster, aber nur für Request-Scoped dependencies (DB-Session, Current-User). - ---- - -### K7: F-CORE-06 — API-First Architecture — TEILWEISE - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Alle Features über API | API-First | **Teilweise** — API routes existieren für alle Module | -| UI ist API-Client | UI nutzt API | **❌ Jinja2 rendert server-side** | -| API versioniert | z.B. /api/v1/ | **❌ Keine Versionierung** | -| OpenAPI/Swagger | Auto-gen, dokumentiert | **⚠️ FastAPI auto-gen existiert, aber nicht explizit konfiguriert** | -| Plugin-API-Endpunkte | Registrierbar | **N/A** | -| KI-Copilot nutzt API | Gleiche Endpunkte | **Nicht implementiert** | - -**Evidence:** -- API routes: `/api/companies`, `/api/contacts`, `/api/dms/*`, `/api/tags/*`, `/api/calendars`, `/api/entries`, `/api/notifications`, `/api/auth/*` -- Kein `/api/v1/` Prefix — alle routes sind unversioniert -- FastAPI generiert automatisch OpenAPI unter `/openapi.json`, aber nicht explizit konfiguriert oder dokumentiert -- HTML routes existieren parallel (`/login`, `/` dashboard) — UI ist NICHT API-Client - ---- - -### K8: F-CORE-07 — Async Job Queue — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Queue-System | Background-Jobs asynchron | **Nicht implementiert** | -| Retry-Logic | Automatische Retries | **Nicht vorhanden** | -| Dead-Letter-Queue | Bei wiederholtem Fehlschlag | **Nicht vorhanden** | -| Job-Status UI | Sichtbar im UI | **Nicht vorhanden** | - -**Evidence:** `grep -rn 'celery\|Celery\|queue\|Queue\|async_job\|background_job\|job_queue' app/` → **0 Treffer** - ---- - -### K9: F-CORE-08 — Caching-Strategie — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Cache-Backend | Sessions, Query-Cache, Plugin-Data | **Nicht implementiert** | -| Cache-Invalidierung | Event-basiert | **N/A** | -| TTL-Caching | Fallback | **Nur `@lru_cache` für Settings** | - -**Evidence:** `grep -rn 'cache\|Cache\|redis\|Redis' app/` → nur `functools.lru_cache` in `config.py` für Settings-Caching. Kein Redis, kein Query-Cache. - ---- - -### K10: F-CORE-09 — User-Profile und Preferences — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| User-Profile | Profil mit Preferences | **Nicht implementiert** | -| Sprache/Zeitzone/Theme | Umschaltbar | **Nicht vorhanden** | -| Dashboard-Konfiguration | Konfigurierbar | **Nicht vorhanden** | -| Plugin-Preferences | Eigene Felder registrierbar | **N/A** | - -**Evidence:** `User`-Modell hat nur: id, username, password_hash, role, personal_folder_id, default_calendar_id, created_at. Keine Preferences, keine Sprache, keine Zeitzone. - ---- - -### K11: F-CORE-10 — Storage-Backend — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| S3-kompatibel | Konfigurierbar | **Nicht implementiert** | -| Lokales Volume | Alternative | **Lokales Dateisystem** | -| Presigned-URLs | Download ohne Plugin-Code | **Nicht vorhanden** | -| Storage-Service | Core-Service für Plugins | **Direkter Dateizugriff** | - -**Evidence:** -- `config.py`: `dms_storage_path: str = Field(default="/data/dms")` — lokales Verzeichnis -- `dms_service.py`: Direkter Dateizugriff via `open()`, `Path`-Operationen -- Keine S3/MinIO/boto3-Integration -- `grep -rn 's3\|S3\|boto3\|storage_backend\|presigned' app/` → **0 Treffer** - ---- - -### K12: F-CORE-11 — Generic Import/Export Service — TEILWEISE - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| CSV-Import | Mit Preview, Dry-Run, Fehler-Reporting | **⚠️ Nur direkter Import ohne Preview/Dry-Run** | -| Excel-Export | Feld-Auswahl, Filterung | **⚠️ CSV + XLSX Export, aber begrenzte Feld-Auswahl** | -| Plugin-Definitionen | Registrierbar | **N/A** | - -**Evidence:** -- `import_service.py`: `import_companies_csv()`, `import_contacts_csv()` — direkter Import, kein Preview, kein Dry-Run -- `export_service.py`: `export_companies_csv()`, `export_companies_xlsx()` — Export funktioniert, aber nicht generisch/plugin-fähig -- Import/Export ist hardcoded für Companies/Contacts, nicht generisch - ---- - -### K13: F-CORE-12 — PDF/Document Generation Service — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| PDF-Generierung | Aus Templates | **Nicht implementiert** | -| Template-Engine | Variablen, Conditionals, Tabellen | **Nicht vorhanden** | -| Storage-Integration | PDFs im Storage gespeichert | **N/A** | - -**Evidence:** `grep -rn 'pdf\|PDF\|weasyprint\|reportlab\|pdfkit' app/` → nur DMS-Preview (stream existing PDFs), keine Generierung - ---- - -### K14: F-CORE-13 — Notification Service — TEILWEISE - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| In-App-Notifications | Bell-Icon, Badge-Zähler | **⚠️ DB-Modell existiert, keine UI** | -| E-Mail-Channel | Notifications per Mail | **Nicht implementiert** | -| Preferences | Pro User konfigurierbar | **Nicht vorhanden** | -| Tenant-Isolation | Pro Tenant isoliert | **N/A (single-tenant)** | -| Plugin-Notification-Typen | Registrierbar | **N/A** | - -**Evidence:** -- `models.py`: `Notification`-Modell existiert (id, user_id, type, title, body, related_entry_id, is_read, created_at) -- `notification_routes.py`: API für List/Mark-Read existiert -- Keine E-Mail-Integration, keine Preferences, kein Badge-Zähler in UI (Jinja2-Templates haben kein Notification-UI) - ---- - -### K15: F-AUTH-01 — Login mit E-Mail — KONFLIKT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Login-Feld | **E-Mail** + Passwort | **Username** + Passwort | -| Session-Cookie | HttpOnly, Secure, SameSite=Strict | SameSite=**lax**, https_only conditional | - -**Evidence:** -- `auth_service.py`: `authenticate_user(db, username, password)` — verwendet `username`, nicht `email` -- `models.py`: `User.username: Mapped[str]` — kein `email`-Feld auf User -- `deps.py`: Session speichert `user_id`, kein Tenant-Kontext -- `main.py`: `same_site="lax"` (requirements sagen Strict), `https_only=settings.is_production` (requirements sagen Secure) - ---- - -### K16: F-AUTH-03 — User-Verwaltung durch Admin — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Admin legt User an | E-Mail, Name, Rolle, Passwort | **Nicht implementiert** | -| User-Tenant-Zuordnung | User wird Tenant zugeordnet | **N/A** | -| Keine Self-Registration | Admin-only | **⚠️ Register-Template existiert** | - -**Evidence:** -- Keine User-Management-Routes (kein `/api/users`, kein Admin-User-CRUD) -- `app/templates/register.html` existiert — Self-Registration-Template (widerspricht Non-Goal #1) -- `init_db.py`: Demo-Seed erstellt nur admin/admin - ---- - -### K17: F-AUTH-05 — Passwort-Reset — FEHLT - -| Aspekt | Requirements | Code-Realität | -|---------|-------------|---------------| -| Reset-Flow | E-Mail mit Reset-Link | **Nicht implementiert** | -| Reset-Link | Gültig 24h | **Nicht vorhanden** | - -**Evidence:** Keine Reset-Routes, keine Reset-Templates, keine Token-Generierung. - ---- - -### K18: F-AUTH-07 — Multi-Tenant — FEHLT (siehe K1) - -Bereits in K1 abgedeckt. Keine Tenant-Modelle, keine User-Tenant-Mapping-Tabelle. - ---- - -### K19: DMS/Calendar/Tags als Core vs Plugin — ARCHITEKTUR-KONFLIKT - -| Modul | Requirements | Code-Realität | -|-------|-------------|---------------| -| DMS | v2-Plugin (F-FILE/F-DMS/F-LINK/F-PERM) | **Core: 3 Modelle + 26KB Service + eigene Routes** | -| Kalender | v2-Plugin (F-CAL-01..18) | **Core: 6 Modelle + 21KB Service + eigene Routes** | -| Tags | v2-Plugin (F-TAG-01..04) | **Core: 2 Modelle + 8KB Service + eigene Routes** | -| Mail | v2-Plugin (F-MAIL-01..19) | **Nicht implementiert** | - -**Evidence:** Alle Module sind direkt in `models.py`, `services/`, `routes/` integriert. Keine Plugin-Grenzen, keine Plugin-Schnittstellen. - -**Hinweis:** Requirements sagen Plugin-System ist v1-Core-Feature, aber die Module selbst sind v2-Plugins. Das bedeutet: In v1 muss das Plugin-System gebaut werden, aber DMS/Kalender/Tags können als v2-Plugins nachgezogen werden. Die bestehenden Implementierungen können als Referenz dienen, müssen aber auf Plugin-Architektur umgebaut werden. - ---- - -## 3. Kompatibel — Was bereits passt - -### ✅ Session-basierte Auth (F-AUTH-01/02, Annahme 12) -- Starlette `SessionMiddleware` mit signed Cookie -- `session_cookie="leocrm_session"`, `max_age` konfigurierbar -- Login setzt `request.session[SESSION_USER_ID_KEY] = user.id` -- Logout cleared session -- **Kompatibel** mit Requirements (Session-basiert, Cookie-basiert) - -### ✅ RBAC Grundgerüst (F-AUTH-04/06) -- 3 Rollen: admin, editor, viewer -- `require_admin` dependency prüft `user.role == "admin"` -- `get_current_user` dependency für auth-geschützte Routes -- **Kompatibel** mit Requirements (3 Rollen v1) - -### ✅ Company/Contact CRUD (F-COMP-01..06, F-CONT-01..07) -- Company: 27 Felder (Name, Adresse, Industrie, Revenue, etc.) -- Contact: 29 Felder (Name, Email, Phone, Title, etc.) -- N:M Junction: `CompanyContact` -- Soft-Delete: `deleted_at` auf beiden -- Pagination, Search, Filter, Sort in Routes -- **Kompatibel** mit Requirements - -### ✅ Data-Features (F-DATA-01..04) -- Pagination: `PageResponse` schema -- Search: Query-Parameter in company/contact routes -- Sort: Sortier-Parameter -- Soft-Delete: `deleted_at` + restore functionality -- **Kompatibel** mit Requirements - -### ✅ Health-Check (F-INFRA-01) -- `/api/health` endpoint, prüft DB, gibt Status + Version -- Nicht auth-geschützt (für Coolify/LB) -- **Kompatibel** mit Requirements - -### ✅ Import/Export Grundgerüst (F-MIG-01, F-DATA-01/02) -- CSV-Import für Companies/Contacts -- CSV + XLSX Export für Companies -- **Teilweise kompatibel** — fehlt Preview, Dry-Run, generische Service-Architektur - -### ✅ DMS-Features (als Referenz für späteres Plugin) -- Folder-Tree mit materialized path -- File-Upload, Preview (PDF), Soft-Delete, Restore -- Entity-Links (N:M zu Companies/Contacts) -- Permissions (Individual/Group/Default) -- Share-Links mit Password + Expiry -- OnlyOffice-Edit-Session -- **Vollständig implementiert** — kann als Plugin-Referenz dienen - -### ✅ Calendar-Features (als Referenz für späteres Plugin) -- Calendar CRUD, Sharing, Visibility-Toggle -- Entries: Events/Tasks/Reminders, Kanban-Status -- Subtasks, Attendees, Entry-Links -- Notifications für Reminders/Invites/Shares -- **Vollständig implementiert** — kann als Plugin-Referenz dienen - -### ✅ Tag-System (als Referenz für späteres Plugin) -- Tag CRUD (admin-only), Color, Assignment -- Bulk-Assign, Entity-Type polymorphic -- **Vollständig implementiert** — kann als Plugin-Referenz dienen - -### ✅ Testing-Setup (F-TEST-01) -- pytest mit 20+ Test-Dateien -- conftest.py mit Fixtures -- Coverage-Messung konfiguriert -- **Teilweise kompatibel** — fehlt Vitest (Frontend) und Playwright (E2E) - ---- - -## 4. F-CORE-Feature-Matrix - -| F-CORE-ID | Feature | Status im Code | Anmerkung | -|-----------|--------|---------------|----------| -| F-CORE-01 | Event Bus | ❌ Nicht implementiert | Keine Event-Infrastruktur | -| F-CORE-02 | Tenant-Isolation | ❌ Nicht implementiert | Kein tenant_id, single-tenant | -| F-CORE-03 | Plugin-DB-Migration | ❌ Nicht implementiert | Kein Plugin-System | -| F-CORE-04 | UI-Plugin-Framework | ❌ Nicht implementiert | Jinja2, keine Plugin-UI | -| F-CORE-05 | Service Container / DI | ❌ Nicht implementiert | Direkte Imports, nur FastAPI Depends | -| F-CORE-06 | API-First Architecture | ⚠️ Teilweise | API routes existieren, aber HTML parallel, keine Versionierung | -| F-CORE-07 | Async Job Queue | ❌ Nicht implementiert | Keine Queue-Infrastruktur | -| F-CORE-08 | Caching-Strategie | ❌ Nicht implementiert | Nur lru_cache für Settings | -| F-CORE-09 | User-Profile/Preferences | ❌ Nicht implementiert | User hat nur username/role | -| F-CORE-10 | Storage-Backend | ❌ Nicht implementiert | Lokales Dateisystem, kein S3 | -| F-CORE-11 | Generic Import/Export | ⚠️ Teilweise | CSV/XLSX funktioniert, nicht generisch, kein Preview/Dry-Run | -| F-CORE-12 | PDF Generation | ❌ Nicht implementiert | Keine PDF-Generierung | -| F-CORE-13 | Notification Service | ⚠️ Teilweise | DB-Modell + API existiert, keine UI, kein E-Mail-Channel | - -**Bilanz:** 0/13 vollständig implementiert, 3/13 teilweise, 10/13 fehlen komplett. - ---- - -## 5. Empfehlung: Was vor Phase 2 angepasst werden muss - -### Priorität 1 — Fundamentale Architektur (vor allem anderen) - -1. **Datenbank-Migration: SQLite → PostgreSQL** - - `config.py`: `database_url` auf PostgreSQL umstellen - - `session.py`: SQLite-PRAGMAs entfernen, PostgreSQL-Engine konfigurieren - - `pyproject.toml`: `psycopg[binary]` oder `asyncpg` hinzufügen - - `docker-compose.yml`: PostgreSQL-Service hinzufügen - -2. **Multi-Tenant-Architektur** - - Neues `Tenant`-Modell + `UserTenant`-Mapping-Tabelle - - `tenant_id`-Spalte auf ALLE Core-Tabellen (Company, Contact, Folder, File, Tag, Calendar, etc.) - - ORM-Query-Filter: automatische tenant_id-Filterung (SQLAlchemy Event oder Query-Wrapper) - - Session-Kontext: aktiver tenant_id in Session speichern - - Tenant-Switch-Endpoint + UI - -3. **Frontend-Wechsel: Jinja2 → React SPA** - - React-Projekt-Setup (Vite + React + TypeScript) - - API-Client-Layer (fetch/axios gegen /api/* Endpunkte) - - Jinja2-Templates und html_routes.py werden obsolet - - i18n-Integration (DE + EN) - - UI-Plugin-Framework vorbereiten (F-CORE-04) - -### Priorität 2 — Core-Infrastructure (F-CORE) - -4. **Service Container / DI (F-CORE-05)** - - Zentralen Service-Container implementieren - - Core-Services registrieren: DB, Cache, Event Bus, Auth, Config, Logger - - Plugin-Schnittstelle für Service-Requests definieren - -5. **Event Bus (F-CORE-01)** - - Event-Publish/Subscribe-System implementieren - - Typisierte Events mit Payload - - Asynchrone Verarbeitung (ggf. via Job Queue) - -6. **Plugin-System (F-PLUGIN-01/02)** - - Plugin-Manifest-Format definieren - - Lifecycle-Hooks: install, activate, deactivate, uninstall - - Plugin-Registry + Loader - - Plugin-API-Endpunkt-Registrierung - - Plugin-DB-Migration (F-CORE-03) - - Plugin-Abhängigkeiten - -7. **API-Versionierung (F-CORE-06)** - - `/api/v1/` Prefix für alle API-Routes - - OpenAPI/Swagger explizit konfigurieren und dokumentieren - - HTML-Routes entfernen (UI wird React SPA = API-Client) - -### Priorität 3 — Weitere Core-Infrastructure - -8. **Async Job Queue (F-CORE-07)** — Queue-System für Background-Jobs -9. **Caching (F-CORE-08)** — Redis-Anbindung, Query-Cache, Cache-Invalidierung -10. **Storage-Backend (F-CORE-10)** — S3-kompatibler Storage-Service -11. **User-Profile/Preferences (F-CORE-09)** — Profil-Erweiterung, Preferences -12. **Notification Service (F-CORE-13)** — E-Mail-Channel, Preferences, Badge-UI -13. **PDF Generation (F-CORE-12)** — Template-Engine, PDF-Generierung -14. **Generic Import/Export (F-CORE-11)** — Generischer Service, Preview, Dry-Run - -### Priorität 4 — Auth-Ergänzungen - -15. **Login auf E-Mail umstellen (F-AUTH-01)** — username → email -16. **User-Verwaltung durch Admin (F-AUTH-03)** — Admin-CRUD für User, Tenant-Zuordnung -17. **Passwort-Reset (F-AUTH-05)** — Reset-Flow mit E-Mail -18. **Register-Template entfernen** — Self-Registration ist Non-Goal -19. **Cookie-Security anpassen** — SameSite=Strict, Secure immer - -### Was beibehalten werden kann - -- **Backend-Services** (company_service, contact_service, etc.) — Business-Logik ist solide -- **Pydantic-Schemas** — Können für API-Validierung weiterverwendet werden -- **DB-Modelle** — Felder/Beziehungen sind korrekt, müssen nur tenant_id ergänzt werden -- **Test-Suite** — pytest-Tests können erweitert werden -- **DMS/Calendar/Tag-Implementierungen** — Als Referenz für spätere Plugin-Entwicklung behalten - ---- - -## 6. Zusammenfassung - -| Kategorie | Anzahl | Status | -|-----------|--------|--------| -| Kritische Konflikte | 4 | Multi-Tenant, Plugin-System, DB, Frontend | -| F-CORE fehlend | 10/13 | Event Bus, Tenant-Isolation, Plugin-Migration, UI-Plugin, Service Container, Job Queue, Caching, User-Profile, Storage, PDF | -| F-CORE teilweise | 3/13 | API-First, Import/Export, Notification | -| F-CORE vollständig | 0/13 | — | -| Auth-Konflikte | 4 | Login (username vs email), User-Verwaltung, Passwort-Reset, Cookie-Security | -| Kompatibel | 7+ | Session-Auth, RBAC, Company/Contact CRUD, Data-Features, Health, DMS/Calendar/Tags (als Referenz) | - -**Fazit:** Die bestehende Codebase ist eine funktionsfähige v0.1-Implementierung (Single-Tenant, SQLite, Jinja2), die den bereinigten v1-Requirements in 4 kritischen Bereichen nicht entspricht: Multi-Tenant, Plugin-System, PostgreSQL, React SPA. 10 von 13 F-CORE-Features fehlen komplett. Die bestehende Business-Logik (Services, Schemas, Modelle) ist jedoch solide und kann als Basis für den Umbau dienen. Der Aufwand für Phase 2 ist erheblich — es handelt sich um eine Architektur-Migration, nicht um inkrementelle Erweiterungen. diff --git a/docs/extracted-architecture-details.md b/docs/extracted-architecture-details.md deleted file mode 100644 index 621a0e5..0000000 --- a/docs/extracted-architecture-details.md +++ /dev/null @@ -1,1006 +0,0 @@ -# LeoCRM — Extracted Architecture Details - -**Zweck:** Implementierungs-Details, die aus der bereinigten `requirements.md` entfernt wurden. Für den Solution Architect als Referenz. -**Quelle:** Original `requirements.md` (git HEAD, 1956 Zeilen) vs. bereinigte Version (2105 Zeilen) -**Datum:** 2026-06-28 - ---- - -## 1. Tech-Stack (vollständige Spezifikation) - -### Original Tech-Stack-Tabelle (entfernt) - -| Komponente | Entscheidung | Bemerkung | -|------------|-------------|----------| -| Backend | FastAPI + SQLAlchemy 2.0 | Python 3.12 | -| Datenbank | PostgreSQL (empfohlen) | Siehe DB-Empfehlung unten | -| Frontend | React SPA | Client-side rendering, i18n (bestätigt durch Prototyp) | -| Deployment | Coolify (Docker) | Bestätigt | -| Linting | ruff + black | Python-Standard | -| Testing | pytest + Vitest/Jest | Backend + Frontend | - -### DB-Empfehlung: PostgreSQL statt SQLite - -Ursprünglich war SQLite spezifiziert. Bei 200.000 Kontakten + mehreren Usern + Concurrent Writes ist SQLite limitiert: - -- **SQLite:** File-Level Locking, nur 1 Writer gleichzeitig, ~15 Updates/Sekunde bei Concurrent Writes (StackOverflow Benchmark) -- **PostgreSQL:** True Concurrent Writers, ~1.500 Updates/Sekunde, MVCC-Architektur (tableone.dev Benchmark) -- **Fazit:** Bei 200k Datensätzen + Multi-User + gleichzeitige Schreibzugriffe → PostgreSQL - -### v0.1 Historischer Tech-Stack (archiviert) -- ~~Backend: FastAPI (Python 3.11+)~~ → Python 3.12 -- ~~DB: SQLite (Datei `leocrm.db`)~~ → PostgreSQL 16 -- ~~Templates: Jinja2 (HTML)~~ → React 18 SPA -- ~~Server: Uvicorn / Gunicorn~~ → Uvicorn (async) -- ~~Deployment: 1 Container Docker Compose~~ → Multi-Container (Backend + Frontend + PostgreSQL + OnlyOffice + Worker) - ---- - -## 2. Domain Knowledge (entfernte Referenzen) - -**Domain:** Customer Relationship Management (CRM) - -Standard-Entitäten in CRMs (Referenz: Zoho CRM, HubSpot, Salesforce): -- **Accounts (Firmen):** Name, Adresse, Industrie, Employees, Revenue, Owner, Phone, Website, Billing/Shipping Address -- **Contacts (Kontaktpersonen):** Name, Email, Phone, Mobile, Title, Department, Mailing Address, Reports To, Description -- **Beziehungen:** N:M (ein Kontakt kann mehreren Firmen zugeordnet sein) - -Quellen: -- Zoho CRM Standard Fields Accounts: https://help.zoho.com/portal/en/kb/crm/sales-force-automation/accounts/articles/standard-fields-accounts -- Zoho CRM Standard Fields Contacts: https://help.zoho.com/portal/en/kb/crm/sales-force-automation/contacts/articles/standard-fields-contacts -- Encore Business Solutions — 30 CRM Custom Account Fields -- Bitrix24 Standard fields in CRM - ---- - -## 3. HTTP-Endpunkt-Spezifikationen - -### F-AUTH-01: Login -- **Endpoint:** POST `/api/auth/login` -- **Akzeptiert:** E-Mail + Passwort -- **Response:** Session-Cookie (HttpOnly+Secure+SameSite=Strict) oder 401 - -### F-AUTH-02: Logout -- **Clientseitig:** Token wird entfernt -- **Server:** Token-Blacklist optional für v1 - -### F-AUTH-03: User-Verwaltung -- **Endpoint:** POST `/api/users` -- **Auth:** Admin-Token erforderlich -- **Response:** 201 (erstellt) oder 403 (ohne Admin-Token) - -### F-AUTH-04: RBAC -- **Jeder API-Endpoint prüft Rolle:** falsche Rolle → 403; korrekte Rolle → 200/201 -- **Beispiele:** POST `/api/users` → 403 (editor); POST `/api/companies` → 403 (viewer), GET `/api/companies` → 200 (viewer) - -### F-AUTH-05: Passwort-Reset -- **Request:** POST `/api/auth/password-reset/request` → sendet E-Mail -- **Confirm:** POST `/api/auth/password-reset/confirm` mit Token + neuem Passwort → aktualisiert Passwort - -### F-COMP-01: Firma anlegen -- **Endpoint:** POST `/api/companies` -- **Response:** 201 Created (valide Daten); 422 Validation Error (ohne Name) - -### F-COMP-02: Firma anzeigen -- **Endpoint:** GET `/api/companies/{id}` -- **Response:** 200 mit Firmendaten + Kontakte-Array; 404 (nicht-existent) - -### F-COMP-03: Firma bearbeiten -- **Endpoint:** PUT/PATCH `/api/companies/{id}` -- **Response:** 200 mit aktualisierten Daten; 422 (Validierungsfehler) - -### F-COMP-04: Firma löschen (Soft-Delete) -- **Endpoint:** DELETE `/api/companies/{id}?cascade=true|false` -- **Response:** 200; Firma wird als `deleted_at = NOW()` markiert -- **Cascade:** bei cascade=true auch Kontakte soft-deleted - -### F-COMP-05: Firmen-Liste mit Pagination -- **Endpoint:** GET `/api/companies?page=2&page_size=25&sort_by=name&sort_order=desc` -- **Response:** 200 mit `{items, total, page, page_size}` - -### F-COMP-06: Firmen-Suche & Filter -- **Endpoint:** GET `/api/companies?search=Tech&industry=IT&country=Germany` -- **Response:** 200 mit gefilterten Ergebnissen - -### F-COMP-07: Audit-Log -- **Tabelle:** `audit_log` -- **Mechanismus:** Middleware/Decorator loggt schreibende Aktionen -- **Endpoint:** GET `/api/audit-log` (Admin, paginiert) - -### F-COMP-08: DSGVO / Right to be Forgotten -- **Endpoint:** DELETE `/api/contacts/{id}?gdpr=true` → harte Löschung -- **Tabelle:** separate `deletion_log` Tabelle (unveränderlich) - -### F-CONT-01: Kontaktperson anlegen -- **Endpoint:** POST `/api/contacts` -- **Response:** 201 (valide Daten); 422 (ohne last_name) -- **N:M:** mit company_ids → N:M-Verknüpfungen erstellt - -### F-CONT-02: Kontaktperson anzeigen -- **Endpoint:** GET `/api/contacts/{id}` -- **Response:** 200 mit Kontaktdaten + Firmen-Array; 404 - -### F-CONT-03: Kontaktperson bearbeiten -- **Endpoint:** PUT/PATCH `/api/contacts/{id}` -- **Response:** 200; Änderung an company_ids → N:M-Tabelle aktualisiert - -### F-CONT-04: Kontaktperson löschen (Soft-Delete) -- **Endpoint:** DELETE `/api/contacts/{id}` -- **Response:** 200; `deleted_at` gesetzt; N:M-Einträge gelöscht; Firmen unberührt - -### F-CONT-05: Kontakt-Liste mit Pagination -- **Endpoint:** GET `/api/contacts?page=1&page_size=25&sort_by=last_name&sort_order=asc` -- **Response:** 200 mit paginierten Ergebnissen -- **Performance:** Response-Zeit <500ms bei 200k Datensätzen (mit DB-Index) - -### F-CONT-06: Kontakt-Suche & Filter -- **Endpoint:** GET `/api/contacts?search=Müller&company_id=5` -- **Response:** 200 mit gefilterten Ergebnissen - -### F-CONT-07: N:M Firmen-Kontakt-Zuordnung -- **Tabelle:** `company_contacts` (N:M-Verknüpfungstabelle) -- **Zuordnung:** POST `/api/companies/{id}/contacts/{contact_id}` -- **Entfernung:** DELETE `/api/companies/{id}/contacts/{contact_id}` - -### F-DATA-01: CSV-Export -- **Endpoint:** GET `/api/companies/export?format=csv&filters=...` -- **Response:** 200 mit `Content-Type: text/csv`, Datei-Download - -### F-DATA-02: Excel-Export -- **Endpoint:** GET `/api/companies/export?format=xlsx&filters=...` -- **Response:** 200 mit `Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` - -### F-DATA-03: Daten-Validierung -- **Technologie:** Pydantic-Schemas für alle Entities -- **Response:** ungültige Eingaben → 422 mit detailiertem Fehler-Objekt - -### F-DATA-06: ARIA-Rollen auf DataTable -- **ARIA-Rollen:** `role="table"`, `role="row"`, `role="columnheader"`, `role="cell"` -- **Sortierung:** `aria-sort="ascending|descending|none"` -- **Label:** beschreibendes `aria-label` -- **Testing:** NVDA und axe DevTools - -### F-UI-01: Responsive Design -- **Breakpoints:** Mobile-First; Touch-Targets min 44px -- **Testing:** Chrome DevTools (375px, 768px, 1920px) - -### F-UI-02: Internationalisierung -- **Library:** react-i18next -- **Locale-Files:** JSON-Locale-Files für DE/EN -- **Persistenz:** Sprachwahl persistiert in User-Settings - -### F-UI-03: Error-Handling & Toast-Notifications -- **Komponente:** Toast-Komponente (success/error/warning/info) -- **Routen:** dedizierte 404/500-Routen im Frontend - -### F-UI-04: Loading-States -- **Komponenten:** Loading-State-Komponenten; Skeleton-Loader -- **State:** API-Loading-State im Frontend-Store; Button-Disabled-State während Pending - -### F-UI-05: Empty-States -- **Komponente:** Empty-State-Komponenten mit Icon + Text + CTA - -### F-UI-06: Confirmation-Dialogs -- **Komponente:** Modal-Dialog-Komponente -- **Regel:** alle DELETE-Operationen erfordern Bestätigung - -### F-SEC-01: CSRF-Schutz -- **Implementierung:** SameSite=Strict Cookies + Origin-Header-Validierung -- **Kein Double-Submit-Token** -- **Middleware:** Origin-Header-Validierung-Middleware -- **Exempt:** nur GET/HEAD/OPTIONS - -### F-SEC-02: XSS-Schutz & Input-Sanitization -- **Frontend:** Output-Encoding -- **Backend:** Pydantic-Validierung strippt gefährliche Eingaben -- **Header:** CSP-Header gesetzt - -### F-SEC-03: Session-Timeout -- **Timeout:** 8h -- **Interceptor:** 401-Interceptor im Frontend → Redirect zur Login-Seite -- **Refresh:** Refresh-Token optional für v1 - -### F-INFRA-01: Health-Check Endpoint -- **Endpoint:** GET `/api/health` -- **Response:** 200 `{status: "healthy", db: "connected"}` oder 503 `{status: "unhealthy", db: "disconnected"}` -- **Auth:** kein Auth erforderlich -- **Coolify:** nutzt Endpoint für Health-Check - -### F-INFRA-02: Backup & Restore -- **Backup:** Docker-Volume-Backup konfiguriert -- **Doku:** Restore-Dokumentation in README - -### F-INFRA-03: Logging -- **Format:** Python `logging` mit JSON-Formatter -- **Konfiguration:** Log-Level konfigurierbar via Env-Var -- **Inhalt:** Method, Path, Status, Duration; Error-Logs mit Stacktrace - -### F-INFRA-04: Monitoring & Alerting -- **Monitoring:** Coolify-Built-in-Monitoring nutzt `/api/health` -- **Optional:** externes Monitoring-Tool - -### F-MIG-01: CSV-Import -- **Endpoint:** POST `/api/import` mit CSV-Datei + Entity-Type -- **Response:** Import-Job mit success/skipped/failed counts - -### F-INT-01: E-Mail-Integration (Passwort-Reset) -- **Config:** SMTP via Env-Vars (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS) -- **Templates:** E-Mail-Templates für Reset - -### F-INT-02: API-Keys / Token-Auth -- **Auth:** Session-Auth-Middleware auf allen Endpoints außer `/api/health` und `/api/auth/*` -- **Optional:** API-Key für externe Integrationen (post-MVP) - -### F-TEST-01: Testing-Strategie -- **Backend:** pytest + httpx für API-Tests; Coverage >80% -- **Frontend:** Vitest für Component-Tests -- **E2E:** Playwright für Critical Paths -- **CI:** Coverage-Report in CI; bei Fehlschlag wird Build blockiert - -### F-ENV-01: Environments & Secrets -- **Env-Vars:** `LEOCRM_SECRET_KEY` (Session-Secret, min 32 Zeichen) -- **Datei:** `.env.example` dokumentiert alle Vars -- **Secrets:** keine Secrets im Git-Repo -- **Coolify:** Env-Vars konfiguriert - -### F-DOC-01: Dokumentation -- **README:** `README.md` mit Setup-Anleitung -- **API-Doku:** `/docs` (Swagger/OpenAPI) auto-gen via FastAPI -- **Admin-Doku:** `docs/admin-guide.md` für Deploy/Backup/Restore - -### F-PERF-01: Performance -- **Response-Zeit:** <500ms für List-Endpunkte bei 200k Datensätzen -- **DB-Indizes:** last_name, first_name, email, company.name, company.industry, company.country -- **Pagination:** verhindert Full-Table-Scan -- **Export:** >50k Datensätze als Background-Job oder Streaming-Response - -### F-A11Y-01: Accessibility (WCAG 2.1 AA) -- **CSS-Utility:** `.sr-only` CSS-Klasse (visual-hidden pattern) -- **Kontrast:** >4.5:1 -- **Testing:** axe DevTools - -### F-A11Y-02: prefers-reduced-motion -- **CSS:** `@media (prefers-reduced-motion: reduce)` Block in globalen Styles -- **Properties:** alle `transition` und `animation` auf `none` oder `0.01ms` -- **Testing:** Chrome DevTools (Rendering → Emulate CSS prefers-reduced-motion: reduce) - -### F-A11Y-03: 44px Touch-Targets -- **CSS-Regel:** `min-height: 44px; min-width: 44px;` für alle interaktiven Elemente -- **Alternative:** `::after` Pseudo-Element mit 44px Touch-Bereich -- **Testing:** Chrome DevTools (375px Breite, Touch-Emulation) - -### F-SCHED-01: Background-Jobs -- **Queue:** Background-Task-Queue (Celery, ARQ, oder FastAPI BackgroundTasks für v1) -- **Status:** Job-Status-Endpoint -- **Export-Limit:** >50k Datensätze als Background-Job, <50k als direkter Download - ---- - -## 4. DB-Schema-Definitionen (Feld-Tabellen) - -### F-COMP-01: Company-Felder - -| Feld | Typ | Pflicht | Bemerkung | -|------|-----|---------|-----------| -| name | String(100) | ✅ | Firmenname | -| account_number | String(40) | ❌ | Interne Referenznummer | -| industry | Picklist | ❌ | IT, Finance, Manufacturing, etc. | -| account_type | Picklist | ❌ | Customer, Partner, Prospect, etc. | -| ownership | Picklist | ❌ | Public, Private, Government, etc. | -| employees | Integer | ❌ | Anzahl Mitarbeiter | -| annual_revenue | Decimal | ❌ | Jahresumsatz | -| phone | String(30) | ❌ | Haupttelefon | -| fax | String(30) | ❌ | Fax | -| email | Email | ❌ | Allgemeine E-Mail | -| website | URL | ❌ | Website | -| rating | Picklist | ❌ | Hot, Warm, Cold | -| parent_account_id | FK→Company | ❌ | Muttergesellschaft | -| billing_street | String(250) | ❌ | Rechnungsadresse Strasse | -| billing_city | String(100) | ❌ | Rechnungsadresse Stadt | -| billing_state | String(100) | ❌ | Rechnungsadresse Bundesland | -| billing_postal_code | String(20) | ❌ | Rechnungsadresse PLZ | -| billing_country | String(100) | ❌ | Rechnungsadresse Land | -| shipping_street | String(250) | ❌ | Besuchsadresse Strasse | -| shipping_city | String(100) | ❌ | Besuchsadresse Stadt | -| shipping_state | String(100) | ❌ | Besuchsadresse Bundesland | -| shipping_postal_code | String(20) | ❌ | Besuchsadresse PLZ | -| shipping_country | String(100) | ❌ | Besuchsadresse Land | -| description | Text(32000) | ❌ | Notizfeld | -| sic_code | String(10) | ❌ | Standard Industrial Classification | -| ticker_symbol | String(30) | ❌ | Börsenkürzel | -| account_site | String(80) | ❌ | Standort-Name (z.B. Headquarters) | - -### F-CONT-01: Contact-Felder - -| Feld | Typ | Pflicht | Bemerkung | -|------|-----|---------|-----------| -| first_name | String(50) | ❌ | Vorname | -| last_name | String(50) | ✅ | Nachname | -| salutation | Picklist | ❌ | Herr, Frau, Dr., etc. | -| email | Email | ❌ | Haupt-E-Mail | -| secondary_email | Email | ❌ | Zweit-E-Mail | -| phone | String(30) | ❌ | Bürotelefon | -| mobile | String(30) | ❌ | Mobiltelefon | -| home_phone | String(30) | ❌ | Privattelefon | -| fax | String(30) | ❌ | Fax | -| title | String(100) | ❌ | Jobtitel (CEO, Manager, etc.) | -| department | String(100) | ❌ | Abteilung | -| reports_to | FK→Contact | ❌ | Vorgesetzter | -| date_of_birth | Date | ❌ | Geburtsdatum | -| assistant | String(50) | ❌ | Assistent-Name | -| assistant_phone | String(30) | ❌ | Assistent-Telefon | -| mailing_street | String(250) | ❌ | Postadresse Strasse | -| mailing_city | String(100) | ❌ | Postadresse Stadt | -| mailing_state | String(100) | ❌ | Postadresse Bundesland | -| mailing_postal_code | String(20) | ❌ | Postadresse PLZ | -| mailing_country | String(100) | ❌ | Postadresse Land | -| other_street | String(250) | ❌ | Andere Adresse Strasse | -| other_city | String(100) | ❌ | Andere Adresse Stadt | -| other_state | String(100) | ❌ | Andere Adresse Bundesland | -| other_postal_code | String(20) | ❌ | Andere Adresse PLZ | -| other_country | String(100) | ❌ | Andere Adresse Land | -| skype_id | String(50) | ❌ | Skype | -| linkedin | URL | ❌ | LinkedIn-Profil | -| twitter | String(50) | ❌ | Twitter-Handle | -| description | Text(32000) | ❌ | Notizfeld | -| company_ids | [FK→Company] | ❌ | N:M-Zuordnung (0..n Firmen) | - -### Implizite DB-Tabellen (aus Akzeptanzkriterien extrahiert) - -- `company_contacts` — N:M-Verknüpfungstabelle (company_id ↔ contact_id) -- `audit_log` — Tabelle für Audit-Log-Einträge (user, action, entity, entity_id, timestamp) -- `deletion_log` — Unveränderliche Tabelle für DSGVO-Löschungen -- `users` — User-Tabelle (email, name, role, password_hash) -- `companies` — Firmen-Tabelle (siehe Feld-Tabelle oben, + `deleted_at` TIMESTAMP für Soft-Delete) -- `contacts` — Kontakt-Tabelle (siehe Feld-Tabelle oben, + `deleted_at` TIMESTAMP für Soft-Delete) - ---- - -## 5. Nicht-funktionale Anforderungen (vollständige Tabelle) - -| ID | Kategorie | Anforderung | Metrik | -|----|-----------|-------------|--------| -| NF-01 | Performance | API-Response <500ms bei 200k Datensätzen | 95th Percentile <500ms | -| NF-02 | Skalierung | DB-Connection-Pooling (SQLAlchemy Pool size=10) | Keine Connection-Erschöpfung bei 10 concurrent Users | -| NF-03 | Sicherheit | Alle Passwörter bcrypt-gehashed (cost=12) | Keine Plain-Text-Passwörter in DB | -| NF-04 | Sicherheit | Session-Secret via Env-Var, min 32 Zeichen | App startet nicht ohne Secret | -| NF-05 | Verfügbarkeit | Health-Check für Coolify Auto-Restart | Auto-Restart bei 503 | -| NF-06 | Wartbarkeit | Code-Struktur: `api/`, `models/`, `schemas/`, `services/`, `tests/` | Klare Trennung | -| NF-07 | Wartbarkeit | Linting: ruff + black, pre-commit-hook | CI blockt bei Lint-Fehlern | -| NF-08 | Testbarkeit | pytest Coverage >80% Backend | Coverage-Report in CI | -| NF-09 | i18n | DE + EN, Sprachwahl persistiert | Alle UI-Texte übersetzt | -| NF-10 | Accessibility | WCAG 2.1 AA | axe-Check: 0 Violations | -| NF-11 | Logging | Strukturierte JSON-Logs | `docker logs` zeigt JSON-Einträge | -| NF-12 | Deployment | Docker-Container auf Coolify | `docker-compose.yml` + `Dockerfile` | - ---- - -## 6. Annahmen (entfernte Details) - -1. **Multi-Tenant (Multi-Company):** v1 ist Multi-Tenant (Multi-Company) — mehrere Organisationen, mehrere User. -2. **PostgreSQL statt SQLite:** Bei 200k Kontakten + Multi-User + Concurrent Writes wird PostgreSQL empfohlen. SQLite limitiert auf ~15 concurrent updates/sec vs PostgreSQL ~1500/sec. -3. **SPA-Frontend:** Client-side rendering mit React SPA (bestätigt durch genehmigten Prototyp leocrm-prototype-x7k2p9, v7). -4. **Max 10 concurrent Users:** v1 ist für kleine Teams, nicht für Enterprise. -5. **E-Mail-Versand:** SMTP via externem Provider (Mailtrap für Dev, Production-SMTP für Prod). -6. **Soft-Delete als Default:** Firmen und Kontakte werden soft-deleted (recoverable), außer DSGVO-Löschung (hard delete). -7. **Rollensystem v1:** 3 Rollen: admin, editor, viewer. -8. **Export-Limit:** Export von >50k Datensätzen als Background-Job, <50k als direkter Download. -9. **i18n-Default:** Deutsch ist Default-Sprache, Englisch ist zweites Locale. -10. **Python 3.12:** Aktuelle stabile Python-Version. - ---- - -## 7. Technologie-Wahlen (Alle Module) - -### Core-Stack -| Technologie | Verwendung | -|-------------|-----------| -| FastAPI | Backend-Framework | -| SQLAlchemy 2.0 | ORM | -| PostgreSQL 16 | Datenbank | -| React 18 SPA | Frontend | -| Uvicorn (async) | ASGI-Server | -| ruff + black | Linting | -| pytest + httpx | Backend-Tests | -| Vitest | Frontend-Tests | -| Playwright | E2E-Tests | -| Pydantic | Input-Validierung | -| bcrypt (cost=12) | Passwort-Hashing | -| Celery / ARQ / FastAPI BackgroundTasks | Background-Jobs | -| react-i18next | i18n-Frontend | -| Docker Compose | Multi-Container-Deployment | -| Coolify | Deployment-Plattform | - -### DMS-Modul -| Technologie | Verwendung | -|-------------|-----------| -| PDF.js | PDF-Preview im Browser | -| OnlyOffice Document Server | Online-Bearbeitung (separater Container) | - -### Mail-Modul -| Technologie | Verwendung | -|-------------|-----------| -| IMAP4rev1 (RFC 3501) | Mail-Empfang | -| IMAP IDLE (RFC 2177) | Real-time Push für neue Mails | -| SMTP Submission (RFC 6409) | Mail-Versand | -| DOMPurify | HTML-Sanitization (XSS-Schutz) | -| python-gnupg | PGP-Verschlüsselung (OpenPGP, RFC 4880) | -| PostgreSQL Full-Text Search (tsvector) | Volltext-Suche über Mails | -| AES-256 | Passwort-Verschlüsselung für IMAP/SMTP-Credentials | - ---- - -## 8. Farbcodes (Hex-Werte) - -### Kalender-Modul (F-CAL-06) - -| Eintrags-Typ | Subtyp | Farbe (Hex) | -|-------------|-------|-------------| -| appointment | normal | `#3B82F6` (blau) | -| task | normal | `#F59E0B` (gelb) | -| * | follow_up | `#F97316` (orange) | -| * | private | `#9CA3AF` (grau) | - -### Tag-System (F-TAG-02) -- Tags können mit Farbe versehen werden, z.B. `#FF0000` für „VIP" - ---- - -## 9. Protokoll-Details (Mail-Modul) - -### IMAP -- **Protokoll:** IMAP4rev1 (RFC 3501) -- **IDLE:** RFC 2177 für Push, Fallback: Polling alle 5 Min -- **Port:** 993 (SSL/TLS) -- **Auth:** PLAIN/LOGIN -- **MOVE:** RFC 6851 (Fallback: COPY + STORE \\Deleted + EXPUNGE) -- **Flags:** `\\Seen`, `\\Flagged`, `\\Answered`, `\\Draft`, `$Spam` -- **Kompatibilität:** Getestet mit Dovecot, Courier IMAP - -### SMTP -- **Protokoll:** SMTP Submission (RFC 6409) -- **Port 587:** mit STARTTLS -- **Port 465:** mit SSL -- **Auth:** PLAIN/LOGIN mit Benutzername+Passwort - -### PGP -- **Standard:** OpenPGP (RFC 4880) -- **Library:** python-gnupg -- **Private Key:** verschlüsselt gespeichert (AES-256 + Passphrase) -- **Passphrase:** nicht gespeichert, nur im Session-Cache -- **Public Key:** pro Kontakt speicherbar -- **S/MIME:** post-MVP - -### Attachment-Storage -- Anhänge werden lokal gespeichert (Dateisystem oder S3) -- Nicht im IMAP-Server belassen (Caching) -- Mail-Body wird in PostgreSQL gespeichert -- Anhänge bei Bedarf vom IMAP-Server nachgeladen -- Max: 25 MB pro Anhang, 50 MB pro Mail - -### Passwort-Speicherung -- IMAP/SMTP-Passwörter AES-256 verschlüsselt -- Key via Env-Var `MAIL_ENCRYPTION_KEY` - -### Ordner-Mapping -- INBOX → Posteingang -- Sent → Postausgang -- Drafts → Entwürfe -- Spam/Junk → Spam - -### Threading -- Basierend auf `References`- und `In-Reply-To`-Headern (RFC 5322) - -### HTML-Rendering -- Sanitization mit DOMPurify (XSS-Schutz) -- Inline-Styles erlaubt, Scripts/IFrames entfernt -- Plain-Text-Fallback - ---- - -## 10. RFC-Referenzen (alle) - -| RFC | Titel | Verwendung | -|-----|-------|-----------| -| RFC 3501 | IMAP4rev1 | Mail-Empfang | -| RFC 2177 | IMAP IDLE | Real-time Push | -| RFC 6851 | IMAP MOVE | Mail verschieben | -| RFC 6409 | SMTP Submission | Mail-Versand | -| RFC 4880 | OpenPGP | PGP-Verschlüsselung | -| RFC 5322 | Internet Message Format | Threading (References/In-Reply-To) | -| RFC 5545 | iCalendar | Recurrence Rules (RRULE) für wiederkehrende Termine | -| RFC 6047 | iMIP | (Non-Goal: nicht in v2-mail) | -| RFC 8620 | JMAP | (Non-Goal: nicht unterstützt) | - ---- - -## 11. DMS-Modul: HTTP-Endpunkte & Architektur-Details - -### F-DMS-01: Ordner-Struktur -- **Create:** POST `/api/dms/folders` → 201 -- **Rename/Move:** PATCH `/api/dms/folders/{id}` → 200 -- **Delete:** DELETE `/api/dms/folders/{id}` → 200 (Soft-Delete) -- **Zirkuläre Verschiebung:** serverseitig verhindert → 422 - -### F-DMS-02: Datei-Upload -- **Endpoint:** POST `/api/dms/files/upload` (multipart) → 201 mit Datei-Metadaten -- **Max-Size:** 413 bei Überschreitung (Default 100 MB) -- **Status:** WebSocket oder Polling - -### F-DMS-03: Datei-Operationen -- **Rename:** PATCH `/api/dms/files/{id}` → 200 -- **Move:** PATCH `/api/dms/files/{id}` mit `folder_id` → 200 -- **Delete:** DELETE `/api/dms/files/{id}` → 200 (Soft-Delete) -- **Restore:** POST `/api/dms/files/{id}/restore` → 200 - -### F-DMS-04: PDF-Preview -- **Endpoint:** GET `/api/dms/files/{id}/preview` → 200 mit `Content-Type: application/pdf` -- **Frontend:** PDF.js -- **Non-PDF:** 415 Unsupported Media Type - -### F-DMS-05: OnlyOffice Integration -- **Server:** OnlyOffice Document Server als separater Container (Coolify) -- **Endpoint:** POST `/api/dms/files/{id}/edit-session` → 200 mit OnlyOffice-URL -- **Formate:** DOCX, XLSX, PPTX - -### F-DMS-06: Datei-Metadaten -- **Endpoint:** GET `/api/dms/files/{id}` → 200 mit `{name, size, mime_type, uploaded_at, modified_at, uploaded_by}` -- **Größe:** serverseitig in Bytes, clientseitig formatiert - -### F-DMS-07: Datei-Suche -- **Endpoint:** GET `/api/dms/search?q=vertrag&folder_id={id}` → 200 mit `{items: [{type, name, path, ...}]}` -- **Global:** ohne `folder_id` → gesamte DMS - -### F-LINK-01: Dateien mit Firmen verknüpfen -- **Endpoint:** POST `/api/dms/files/{id}/link` mit `{entity_type: "company", entity_id: N}` → 201 -- **Delete:** DELETE `/api/dms/files/{id}/link?entity_type=company&entity_id=N` → 204 -- **Unique Constraint:** Duplikate serverseitig verhindert - -### F-LINK-02: Dateien mit Kontakten verknüpfen -- **Endpoint:** POST `/api/dms/files/{id}/link` mit `{entity_type: "contact", entity_id: N}` → 201 - -### F-LINK-03: Verknüpfte Dateien in Firmen-Detail -- **Response:** GET `/api/companies/{id}` enthält `linked_files: [{id, name, size, mime_type, modified_at}]` - -### F-LINK-04: Verknüpfte Dateien in Kontakt-Detail -- **Response:** GET `/api/contacts/{id}` enthält `linked_files: [{id, name, size, mime_type, modified_at}]` - -### F-LINK-05: Reverse-Verknüpfung -- **Response:** GET `/api/dms/files/{id}` enthält `linked_entities: [{entity_type, entity_id, entity_name}]` -- **Delete:** DELETE `/api/dms/files/{id}/link?entity_type=company&entity_id=N` → 204 - -### F-LINK-06: Mehrfach-Verknüpfung -- **Bulk:** POST `/api/dms/files/bulk-link` mit `{file_ids: [...], entity_type: "company", entity_id: N}` → 201 -- **Ordner:** POST `/api/dms/folders/{id}/link` - -### F-TAG-01: Tags anwenden -- **Assign:** POST `/api/tags/assign` mit `{entity_type, entity_id, tag_id}` → 201 -- **Remove:** DELETE `/api/tags/assign?entity_type=...&entity_id=...&tag_id=...` → 204 -- **Unique Constraint:** (entity_type, entity_id, tag_id) - -### F-TAG-02: Tag-Verwaltung -- **Create:** POST `/api/tags` (Admin) → 201 -- **Update:** PATCH `/api/tags/{id}` → 200 -- **Delete:** DELETE `/api/tags/{id}` → 204 mit Cascade Delete auf `tag_assignments` -- **Non-Admin:** → 403 - -### F-TAG-03: Tag-Filterung -- **Endpoint:** GET `/api/companies?tag_ids=1,2&tag_mode=and` → 200 -- **Modi:** `tag_mode=or` → OR-Verknüpfung -- **Gleiche Parameter** für `/api/contacts`, `/api/dms/files` - -### F-TAG-04: Tag-Cloud -- **Endpoint:** GET `/api/tags?with_counts=true` → 200 mit `[{id, name, color, count}]` - -### F-PERM-01: Persönlicher Root-Ordner -- **User-Creation:** automatisch `personal_folder_id` gesetzt -- **Ownership-Check:** GET `/api/dms/folders/{id}` prüft Ownership → 403 bei fremdem Ordner (außer Admin) - -### F-PERM-02: Gemeinsame Root-Ordner -- **Create:** POST `/api/dms/folders` mit `shared_with_group_id` → 201 -- **Permissions:** `folder_permissions` Tabelle (folder_id, group_id, permission: read|write) - -### F-PERM-03: Datei/Ordner mit Usern teilen -- **Share:** POST `/api/dms/files/{id}/share` mit `{user_id, permission: "read|write"}` → 201 -- **Remove:** DELETE `/api/dms/files/{id}/share?user_id=N` → 204 -- **Shared-with-me:** GET `/api/dms/shared-with-me` → 200 - -### F-PERM-04: Datei/Ordner mit Gruppen teilen -- **Share:** POST `/api/dms/files/{id}/share` mit `{group_id, permission: "read|write"}` → 201 -- **Resolution:** Individual > Group > Default (Deny vor Allow) -- **Remove:** DELETE `/api/dms/files/{id}/share?group_id=N` → 204 - -### F-PERM-05: Share-Links -- **Create:** POST `/api/dms/files/{id}/share-link` mit `{password?, expires_at?, download_only?}` → 201 mit `{url}` -- **Public:** GET `/api/public/share/{token}` → 200 (oder 401 bei Passwort, 410 bei abgelaufen) -- **Public-Endpoint:** braucht keine Auth - -### F-PERM-06: Berechtigungs-Anzeige -- **Endpoint:** GET `/api/dms/files/{id}/permissions` → 200 mit `{owner: {id, name}, shares: [{user_id?, group_id?, name, permission}], share_links: [{id, url, has_password, expires_at, is_active}]}` - -### F-FILEUI-01: Datei-Browser -- **Komponenten:** `FileBrowser` mit `SidebarTree` + `MainView` (Grid/List Toggle) -- **State-Management:** für aktiven Ordner - -### F-FILEUI-02: Breadcrumb-Navigation -- **Komponente:** `Breadcrumb` mit klickbaren Segmenten -- **Pfad:** aus `folder.path` (Materialized Path oder rekursive Abfrage) generiert - -### F-FILEUI-03: Kontext-Menü -- **Komponente:** `ContextMenu` mit dynamischen Items basierend auf `entity_type` (file/folder) und `permission` (read/write) -- **Mobile:** Long-Press statt Rechtsklick - -### F-FILEUI-04: Bulk-Aktionen -- **Move:** POST `/api/dms/files/bulk-move` mit `{file_ids: [...], target_folder_id}` → 200 -- **Delete:** POST `/api/dms/files/bulk-delete` → 200 -- **Tag:** POST `/api/tags/bulk-assign` → 201 - -### F-FILEUI-05: Upload-Progress -- **Komponente:** `UploadProgress` mit pro-Datei Progress -- **Upload:** via `XMLHttpRequest` (für Progress-Events) oder WebSocket - -### F-FILEUI-06: Drag & Drop -- **Frontend:** HTML5 Drag & Drop API -- **Drop-Target:** validiert Permission clientseitig (grün = erlaubt, rot = verboten) -- **Server:** PATCH `/api/dms/files/{id}` mit `folder_id` → Permission-Check → 200 oder 403 - ---- - -## 12. Kalender-Modul: HTTP-Endpunkte & Architektur-Details - -### Architektur-Hinweis - -Ein Kalender-Eintrag (`CalendarEntry`) hat einen `entry_type`: -- **`appointment`** — Termin mit Start/Ende (Datum+Zeit), optional Ganztägig, Ort, Teilnehmer -- **`task`** — Aufgabe mit Fälligkeitsdatum (due_date), Priorität, Status, Sub-Tasks, Zuständiger — keine feste Uhrzeit - -Beide Typen teilen sich die gleichen Verknüpfungs-, Erinnerungs-, Wiederholungs- und Anzeige-Mechanismen. - -### F-CAL-01: Kalender-Ansichten -- **Frontend:** `CalendarView` Komponente mit `mode: month|week|day` -- **Endpoint:** GET `/api/calendar/entries?from=2026-06-01&to=2026-06-30` → 200 mit Entry-Array - -### F-CAL-02: Kanban-Zeitraum-Ansicht -- **Frontend:** `KanbanCalendar` Komponente mit `period: this_week|next_2_weeks|this_month|custom` -- **Endpoint:** GET `/api/calendar/kanban?period=this_week` → 200 mit `{columns: [{date, appointments: [...], tasks: [...]}]}` - -### F-CAL-03: Kalender-Eintrag erstellen -- **Endpoint:** POST `/api/calendar/entries` → 201 -- **Fields appointment:** `entry_type`, `title`, `description?`, `start_at`, `end_at`, `all_day?`, `location?`, `attendees?` -- **Fields task:** `entry_type`, `title`, `description?`, `due_date?`, `priority: "high|medium|low"`, `status?: "open"` -- **Validierung:** end_at > start_at (außer all_day=true); title nicht leer - -### F-CAL-04: Einträge mit Entitäten verknüpfen -- **Link:** POST `/api/calendar/entries/{id}/link` mit `{entity_type: "company|contact|deal", entity_id: N}` → 201 -- **Response companies:** GET `/api/companies/{id}` enthält `upcoming_events: [...]` und `open_tasks: [...]` - -### F-CAL-05: Drag & Drop im Kalender -- **Frontend:** HTML5 Drag & Drop auf Kalender-Zellen (appointments) und Kanban-Spalten (tasks) -- **Update:** PATCH `/api/calendar/entries/{id}` mit neuem `start_at`/`end_at` (appointment) oder `status` (task) → 200 -- **Optimistic Update** mit Rollback bei Fehler - -### F-CAL-06: Farbcodierung -- **Fields:** `entry_type: appointment|task` und `subtype: normal|follow_up|private` -- **Color Map:** `{appointment+normal: "#3B82F6", task+normal: "#F59E0B", *+follow_up: "#F97316", *+private: "#9CA3AF"}` -- **Update:** PATCH `/api/calendar/entries/{id}` mit `subtype` → 200 - -### F-CAL-07: Erinnerungen/Alerts -- **Field:** `reminder: {value: N, unit: "minutes|hours|days", channel: "in_app|email"}` -- **Background-Job:** prüft fällige Erinnerungen (Cron, minütlich) -- **Notification:** POST `/api/notifications` bei Fälligkeit -- **Überfällig-Check:** täglicher Cron-Job - -### F-CAL-08: Wiederkehrende Einträge -- **Field:** `recurrence: {pattern: "daily|weekly|monthly|yearly|custom", custom_rule?, end_date?, exceptions?: [dates]}` -- **Appointments:** Server generiert Instanzen via RRULE (RFC 5545) -- **Tasks:** bei Status-Wechsel auf `done` → POST `/api/calendar/entries` mit neuem `due_date` -- **Status `cancelled`:** keine Generierung - -### F-CAL-09: Kalender-Feeds (ICS) -- **Export:** GET `/api/calendar/{calendar_id}/ics-feed?token={user_token}` → 200 mit `Content-Type: text/calendar` (RFC 5545 konform) -- **Import:** POST `/api/calendar/import` (multipart ICS, optional calendar_id) → 201 mit `{imported: N, skipped: M}` -- **Auth:** Token-basierte Auth für Feed-URL - -### F-CAL-10: Ressourcen-Booking -- **Create Resource:** POST `/api/resources` (Admin) → 201 -- **Book:** POST `/api/calendar/entries/{id}/book-resource` mit `{resource_id}` → 201 oder 409 bei Konflikt -- **Bookings:** GET `/api/resources/{id}/bookings?from=...&to=...` → 200 - -### F-CAL-11: Mehrere Kalender -- **Create:** POST `/api/calendars` mit `{name, color, type: "personal|team|project|company"}` → 201 -- **Auto-Create:** bei User-Anlage automatisch POST `/api/calendars` mit `{name: "Mein Kalender", type: "personal"}` -- **List:** GET `/api/calendars` → 200 mit sichtbaren Kalendern -- **Delete:** DELETE `/api/calendars/{id}` → 204 (Cascade auf Einträge) - -### F-CAL-12: Kalender abonnieren -- **Frontend:** `CalendarSidebar` mit Toggle-Switches pro Kalender -- **Endpoint:** GET `/api/calendar/entries?calendar_ids=1,3,5` → 200 -- **State:** persistiert pro User (`user_calendar_visibility` Tabelle) - -### F-CAL-13: Kalender teilen -- **Share:** POST `/api/calendars/{id}/share` mit `{user_id?, group_id?, permission: "read|write"}` → 201 -- **Remove:** DELETE `/api/calendars/{id}/share?user_id=N` → 204 -- **Permissions:** GET `/api/calendars/{id}/permissions` → 200 mit `{owner, shares}` - -### F-CAL-14: Default-Kalender -- **Setting:** `default_calendar_id` (User-Setting) -- **Update:** PATCH `/api/users/me/settings` mit `{default_calendar_id: N}` → 200 -- **Fallback:** persönlicher Kalender bei keinem Setting - -### F-CAL-15: Aufgaben-Zuweisung -- **Field:** `assigned_to: user_id` (nur für entry_type=task) -- **Update:** PATCH `/api/calendar/entries/{id}` mit `assigned_to` → 200, sendet Notification -- **Query:** GET `/api/calendar/entries?entry_type=task&assigned_to={user_id}` → 200 - -### F-CAL-16: Sub-Tasks -- **Create:** POST `/api/calendar/entries/{id}/subtasks` mit `{title}` → 201 (nur für entry_type=task) -- **Update:** PATCH `/api/calendar/entries/{id}/subtasks/{sub_id}` mit `{completed: true}` → 200 -- **Response:** GET `/api/calendar/entries/{id}` enthält `subtasks: [{id, title, completed}]` und `progress: {completed, total}` - -### F-CAL-17: Aufgaben-Filter & Liste -- **Query:** GET `/api/calendar/entries?entry_type=task&assigned_to={id}&priority=high&status=open&due_filter=overdue&page=1&page_size=25&sort_by=due_date&sort_order=asc` → 200 mit `{items, total, page, page_size}` -- **Export:** GET `/api/calendar/entries/export?format=csv&entry_type=task&status=open` → 200 mit CSV-Download -- **Frontend:** `TaskKanban` (4 Spalten) und `TaskList` (DataTable) Komponenten - -### F-CAL-18: Bulk-Aktionen -- **Endpoint:** POST `/api/calendar/entries/bulk` mit `{entry_ids: [...], action: "status|assign|due_date|delete", value: ...}` → 200 mit `{updated: N}` - ---- - -## 13. Mail-Modul: HTTP-Endpunkte & Architektur-Details - -### F-MAIL-01: Standard-Ordner -- **Endpoint:** GET `/api/mail/folders` → 200 mit `{folders: [{id, name, type: "inbox|sent|drafts|spam|custom", unread_count, total_count}]}` -- **IMAP IDLE-Listener:** läuft als Background-Task, neue Mails innerhalb von 5 Sekunden -- **Bidirektionale Flag-Sync:** \\Seen, \\Flagged, \\Answered, \\Draft, $Spam - -### F-MAIL-02: E-Mail schreiben -- **Send:** POST `/api/mail/send` mit `{to: [...], cc: [...], bcc: [...], subject, body_html, in_reply_to?, attachments: [...]}` → 201 -- **Draft:** POST `/api/mail/drafts` → speichert Entwurf -- **Reply:** POST `/api/mail/{id}/reply` → erstellt Reply mit vorausgefüllten Feldern -- **Forward:** POST `/api/mail/{id}/forward` → erstellt Forward -- **HTML-Sanitization:** DOMPurify - -### F-MAIL-03: Volltext-Suche -- **Endpoint:** GET `/api/mail/search?q=angebot&folder=inbox&date_from=...&date_to=...` → 200 mit `{items: [{id, subject, from, date, snippet, folder}], total}` -- **Index:** PostgreSQL tsvector-Index über `mail_body_tsv` und `mail_subject_tsv` -- **Performance:** <500ms bei 10.000 Mails - -### F-MAIL-04: Anhänge -- **Send:** POST `/api/mail/send` mit multipart-Form-Data für Anhänge -- **Download:** GET `/api/mail/{id}/attachments/{att_id}` → 200 -- **Content-Disposition:** `attachment` für Downloads, `inline` für inline-Bilder -- **Max:** 25 MB pro Anhang, 50 MB pro Mail - -### F-MAIL-05: Threading -- **Endpoint:** GET `/api/mail/threads?folder=inbox` → 200 mit `{items: [{thread_id, subject, participants, message_count, last_date, messages: [{id, from, date, snippet}]}]}` -- **Basis:** `In-Reply-To`/`References`-Header (RFC 5322) -- **Frontend:** `ThreadView` Komponente - -### F-MAIL-06: Vorlagen/Templates -- **Create:** POST `/api/mail/templates` mit `{name, subject, body_html, shared: false}` → 201 -- **List:** GET `/api/mail/templates?scope=me|shared` → 200 -- **Compose:** POST `/api/mail/compose?template_id=N&contact_id=M&company_id=K` → 200 mit aufgelösten Platzhaltern -- **Syntax:** `{{entity.field}}` - -### F-MAIL-07: Filter/Regeln -- **Create:** POST `/api/mail/rules` mit `{name, conditions: [{field, operator, value}], actions: [{type, value}]}` → 201 -- **Evaluation:** Background-Worker nach IMAP-IDLE-Trigger -- **List:** GET `/api/mail/rules` → 200 -- **Delete:** DELETE `/api/mail/rules/{id}` → 204 - -### F-MAIL-08: Abwesenheitsnotiz -- **Endpoint:** POST `/api/mail/vacation` mit `{active: true, subject, body, start_date, end_date}` → 201 -- **Background-Worker:** prüft eingehende Mails, `vacation_sent_log` Tabelle (einmal pro Absender) -- **No-Reply-Erkennung:** Absender enthält „noreply", „no-reply", „donotreply" - -### F-MAIL-09: Labels/Flags -- **Flag:** PATCH `/api/mail/{id}/flags` mit `{starred: true}` → 200, setzt IMAP \\Flagged -- **Label:** POST `/api/mail/{id}/labels` mit `{label_id: N}` → 201 -- **Filter:** GET `/api/mail?label=vertrieb` → 200 -- **Label-Verwaltung:** POST `/api/mail/labels` mit `{name, color}` - -### F-MAIL-10: Kontakt-Verknüpfung -- **Contact-Mails:** GET `/api/contacts/{id}/emails` → 200 mit `{items: [{id, subject, from, date, direction: "in|out", thread_id}]}` -- **Company-Mails:** GET `/api/companies/{id}/emails` → 200 -- **Auto-Verknüpfung:** Background-Job matcht Absender/Empfänger gegen `contacts.email`-Feld -- **Manual:** POST `/api/mail/{id}/link` mit `{contact_id, company_id}` - -### F-MAIL-11: Kalender-Integration -- **Endpoint:** POST `/api/mail/{id}/create-event` mit `{date, time, duration, calendar_id?, title?}` → 201 -- **Defaults:** Titel = Mail-Betreff, Beschreibung = Mail-Body (plain text), Teilnehmer = Mail-Absender -- **Link:** `calendar_entry.source_mail_id = mail.id` - -### F-MAIL-12: PGP-Verschlüsselung -- **Import Private Key:** POST `/api/mail/pgp/keys` mit `{private_key, passphrase}` → 201 (verschlüsselt gespeichert) -- **Import Public Key:** POST `/api/contacts/{id}/pgp-key` mit `{public_key}` → 201 -- **Encrypt Send:** POST `/api/mail/send` mit `{encrypt: true}` → python-gnupg Verschlüsselung -- **Decrypt:** GET `/api/mail/{id}` → erkennt PGP-Block, entschlüsselt bei vorhandener Passphrase (Session-Cache) - -### F-MAIL-13: Signaturen -- **Create:** POST `/api/mail/signatures` mit `{name, body_html}` → 201 -- **Assign Default:** PATCH `/api/mail/accounts/{id}` mit `{default_signature_id: N}` → 200 -- **Compose:** POST `/api/mail/compose?account_id=N` → Response enthält `signature`-Feld - -### F-MAIL-14: Mehrere Postfächer -- **Create:** POST `/api/mail/accounts` mit `{name, imap_host, imap_port, imap_ssl, smtp_host, smtp_port, smtp_ssl, username, password}` → 201 (Passwort verschlüsselt) -- **List:** GET `/api/mail/accounts` → 200 -- **Default:** PATCH `/api/users/me/settings` mit `{default_mail_account_id: N}` - -### F-MAIL-15: Geteilte Postfächer -- **Create:** POST `/api/mail/accounts` mit `{type: "shared", name: "info@firma.de", ...}` → 201 -- **Assign Users:** POST `/api/mail/accounts/{id}/users` mit `{user_ids: [1, 2, 3]}` → 200 -- **List:** GET `/api/mail/accounts/shared` → 200 -- **Seen-By:** `mail_seen_by` Tabelle (mail_id, user_id, seen_at) - -### F-MAIL-16: Stellvertretung -- **Delegate:** POST `/api/mail/accounts/{id}/delegates` mit `{delegate_user_id, permission: "read|full"}` → 201 -- **Remove:** DELETE `/api/mail/accounts/{id}/delegates?user_id=N` → 204 -- **List:** GET `/api/mail/accounts?include_delegated=true` → 200 mit `delegated: true` Markierung - -### F-MAIL-17: Sende-Berechtigungen -- **Grant:** POST `/api/mail/accounts/{id}/send-permissions` mit `{user_ids: [...]}` → 200 -- **List:** GET `/api/mail/accounts/{id}/send-permissions` → 200 -- **Send-Check:** POST `/api/mail/send` mit `{from_account_id: N}` → prüft, 403 wenn nicht - -### F-MAIL-18: Postfach-Konfiguration -- **Create:** POST `/api/mail/accounts` mit `{imap_host, imap_port, imap_ssl: true, smtp_host, smtp_port, smtp_starttls: true, username, password}` -- **Validierung:** IMAP-Login + SMTP-Login, 422 bei Fehler -- **Verschlüsselung:** AES-256, Key via Env-Var `MAIL_ENCRYPTION_KEY` -- **Maskierung:** GET `/api/mail/accounts/{id}` → Passwort-Feld ist `***` - -### F-MAIL-19: Mail-Ordner verwalten -- **Create:** POST `/api/mail/folders` mit `{name, parent_id?}` → 201, führt IMAP CREATE aus -- **Rename:** PATCH `/api/mail/folders/{id}` mit `{name}` → 200, führt IMAP RENAME aus -- **Delete:** DELETE `/api/mail/folders/{id}` → 204, führt IMAP DELETE aus - ---- - -## 14. Mail-Modul: Technische Constraints (vollständige Tabelle) - -| Constraint | Beschreibung | -|-----------|-------------| -| IMAP-Protokoll | IMAP4rev1 (RFC 3501) — Server muss IMAP IDLE (RFC 2177) für Push unterstützen | -| SMTP-Protokoll | SMTP Submission (RFC 6409) — Port 587 mit STARTTLS oder Port 465 mit SSL | -| Auth | SMTP: PLAIN/LOGIN mit Benutzername+Passwort; IMAP: PLAIN/LOGIN | -| Attachment-Storage | Anhänge lokal gespeichert (DMS-Integration), nicht im IMAP-Server belassen (Caching) | -| IMAP IDLE | Real-time Push; Fallback: Polling alle 5 Min | -| Volltext-Suche | PostgreSQL Full-Text Search (tsvector) über Mail-Body + Anhang-Namen (OCR optional post-MVP) | -| HTML-Rendering | Sanitization mit DOMPurify (XSS-Schutz); Plain-Text-Fallback | -| Verschlüsselung | PGP (OpenPGP, RFC 4880) via python-gnupg; S/MIME post-MVP | -| Mail-Sync | Bidirektionale Sync: IMAP-Flags (Seen/Flagged) synchronisiert | -| Ordner-Mapping | INBOX→Posteingang, Sent→Postausgang, Drafts→Entwürfe, Spam/Junk→Spam | -| Passwort-Speicherung | IMAP/SMTP-Passwörter AES-256 verschlüsselt, Key via Env-Var MAIL_ENCRYPTION_KEY | -| Max Anhang-Größe | 25 MB pro Anhang, 50 MB pro Mail | - ---- - -## 15. Mail-Modul: Annahmen - -1. **IMAP-Server-Kompatibilität:** IMAP4rev1 (RFC 3501), getestet mit Dovecot, Courier IMAP. -2. **SMTP-Auth:** SMTP-Server muss Authentifizierung unterstützen (PLAIN/LOGIN). Kein Open-Relay. -3. **IMAP IDLE:** Server muss RFC 2177 unterstützen. Fallback: Polling alle 5 Minuten. -4. **IMAP MOVE:** Server muss RFC 6851 unterstützen. Fallback: COPY + STORE \\Deleted + EXPUNGE. -5. **Attachment-Storage:** Lokal (Dateisystem oder S3), Caching in PostgreSQL. -6. **Maximale Anhang-Größe:** 25 MB pro Anhang, 50 MB pro Mail. -7. **Passwort-Speicherung:** AES-256 verschlüsselt, Key via `MAIL_ENCRYPTION_KEY`. -8. **HTML-Sanitization:** DOMPurify, Inline-Styles erlaubt, Scripts/IFrames entfernt. -9. **Multi-Tenant (Multi-Company):** Mail-Modul ist Multi-Tenant (Multi-Company). -10. **PGP-Key-Verwaltung:** Private Keys verschlüsselt (AES-256 + Passphrase). Passphrase nicht gespeichert, nur Session-Cache. - ---- - -## 16. Implizite DB-Tabellen (aus allen Akzeptanzkriterien extrahiert) - -| Tabelle | Modul | Zweck | -|---------|-------|-------| -| `users` | Core | User (email, name, role, password_hash, default_calendar_id, default_mail_account_id) | -| `companies` | Core | Firmen (siehe Feld-Tabelle + deleted_at, created_at) | -| `contacts` | Core | Kontakte (siehe Feld-Tabelle + deleted_at, created_at) | -| `company_contacts` | Core | N:M-Verknüpfung (company_id, contact_id) | -| `audit_log` | Core | Audit (user, action, entity, entity_id, timestamp) | -| `deletion_log` | Core | Unveränderliche DSGVO-Lösch-Logs | -| `dms_folders` | DMS | Ordner (id, name, parent_id, owner_id, deleted_at) | -| `dms_files` | DMS | Dateien (id, name, folder_id, size, mime_type, uploaded_at, modified_at, uploaded_by, deleted_at) | -| `file_links` | DMS | Verknüpfung (file_id, entity_type, entity_id) — Unique Constraint | -| `tags` | DMS | Tags (id, name, color) | -| `tag_assignments` | DMS | Zuordnung (entity_type, entity_id, tag_id) — Unique Constraint | -| `folder_permissions` | DMS | (folder_id, group_id, permission: read|write) | -| `file_shares` | DMS | (file_id, user_id?, group_id?, permission) | -| `share_links` | DMS | (file_id, token, password?, expires_at?, download_only?) | -| `calendar_entries` | Kalender | (id, entry_type, title, description, start_at?, end_at?, due_date?, all_day?, location?, priority?, status?, assigned_to?, calendar_id, subtype, reminder, recurrence) | -| `calendars` | Kalender | (id, name, color, type, owner_id) | -| `calendar_entry_links` | Kalender | (entry_id, entity_type, entity_id) | -| `calendar_shares` | Kalender | (calendar_id, user_id?, group_id?, permission) | -| `user_calendar_visibility` | Kalender | (user_id, calendar_id, visible) | -| `subtasks` | Kalender | (entry_id, title, completed) | -| `resources` | Kalender | (id, name, type) — später | -| `resource_bookings` | Kalender | (resource_id, entry_id, from, to) — später | -| `mail_accounts` | Mail | (id, user_id, name, imap_host, imap_port, imap_ssl, smtp_host, smtp_port, smtp_ssl/starttls, username, password_encrypted, type) | -| `mails` | Mail | (id, account_id, folder_id, subject, body_html, body_text, from, to, cc, bcc, date, in_reply_to, references, thread_id, seen, flagged, has_attachments) | -| `mail_attachments` | Mail | (mail_id, filename, mime_type, size, dms_file_id) | -| `mail_folders` | Mail | (id, account_id, name, type, parent_id, unread_count, total_count) | -| `mail_labels` | Mail | (id, name, color) | -| `mail_label_assignments` | Mail | (mail_id, label_id) | -| `mail_rules` | Mail | (id, account_id, name, conditions, actions, priority) | -| `mail_templates` | Mail | (id, user_id, name, subject, body_html, shared) | -| `mail_signatures` | Mail | (id, user_id, name, body_html) | -| `vacation_sent_log` | Mail | (account_id, sender_address, sent_at) | -| `mail_seen_by` | Mail | (mail_id, user_id, seen_at) | -| `mail_account_delegates` | Mail | (account_id, delegate_user_id, permission) | -| `mail_account_send_permissions` | Mail | (account_id, user_id) | -| `pgp_keys` | Mail | (user_id, private_key_encrypted, public_key) | -| `contact_pgp_keys` | Mail | (contact_id, public_key) | -| `notifications` | Core | (id, user_id, type, title, body, created_at, read_at) | - ---- - -## 17. Historische v0.1 Endpoints (archiviert) - -| Endpoint | Historisch | Aktuell | -|----------|-----------|--------| -| POST `/login` | F-1 | POST `/api/auth/login` | -| POST `/logout` | F-2 | POST `/api/auth/logout` | -| GET `/companies` | F-5 | GET `/api/companies` | -| POST `/companies` | F-5 | POST `/api/companies` | -| DELETE `/companies/{id}` | F-8 | DELETE `/api/companies/{id}` | -| POST `/contacts` | F-10 | POST `/api/contacts` | -| GET `/api/health` | F-15 | GET `/api/health` | - -### v0.1 Demo-Seed -- Beim ersten Start: 1 Admin (`admin`/`admin`), 2 Firmen, 3 Kontakte -- Frische DB → Seed-Daten vorhanden -- DB bereits befüllt → Seed überspringt - ---- - -## 18. Non-Goals (alle Module, vollständig) - -### v1 Non-Goals -1. Self-Registration (nur Admin legt User an) -2. ~~Multi-Tenant (Single-Tenant in v1)~~ — Multi-Tenant (Multi-Company) ist v1-Feature -3. Sales Pipeline / Deals -4. Kampagnen-Management -5. Mobile App (Native) — nur Responsive Web-UI -6. Offline-Support -7. AI-Features (Lead-Scoring, Auto-Enrichment) -8. Real-time Collaboration (Google Docs-style) -9. Webhooks für externe Systeme -10. Multi-Currency -11. Advanced Analytics/Dashboards (BI) -12. SSO/OAuth (nur E-Mail/Passwort) -13. Custom Fields (Standard-Felder fest definiert) -14. Two-Factor Auth (post-MVP) -15. PWA - -### v2 DMS Non-Goals -1. Versionierung (History, Diff) -2. Volltext-Suche in Dokumenten (kein OCR, kein Volltext-Index) -3. Eigene Office-Suite (nur OnlyOffice) -4. E-Mail-Attachment aus DMS -5. Externes Sync (WebDAV/Nextcloud) -6. Watermarking - -### v2 Kalender Non-Goals -1. Ressourcen-Booking (nur optional markiert) -2. Time-Tracking -3. Task-Templates -4. Automatisierte Task-Erstellung aus Triggern -5. Gantt-Diagramm -6. Öffentliche Kalender-Sync (außer ICS) - -### v2 Mail Non-Goals -1. Google/Microsoft API (nur direkte IMAP/SMTP) -2. S/MIME (nur PGP in v2-mail) -3. Mail-Server-Hosting (nur Client) -4. Mailinglisten-Management -5. Newsletter-Tool -6. Mail-to-Ticket -7. OCR für Anhänge -8. Kalender-Einladungen per Mail (iMIP, RFC 6047) -9. JMAP (RFC 8620) -10. Push-Benachrichtigungen (Web Push) — nur In-App - ---- - -**Ende der extrahierten Architektur-Details.** diff --git a/docs/infrastructure_audit_report.md b/docs/infrastructure_audit_report.md deleted file mode 100644 index 9974944..0000000 --- a/docs/infrastructure_audit_report.md +++ /dev/null @@ -1,292 +0,0 @@ -# LeoCRM Infrastructure & Deployment Audit Report - -**Audit Date:** 2026-07-30 -**Auditor:** Runtime DevOps Engineer (parallel worker) -**Repository:** /a0/usr/workdir/leocrm-fix -**Live Endpoint:** https://crm.media-on.de/api/v1/health → `{"status":"healthy","version":"1.0.0"}` - ---- - -## Executive Summary - -| Severity | Count | -|----------|-------| -| CRITICAL | 1 | -| HIGH | 3 | -| MEDIUM | 5 | -| LOW | 4 | - -The application is live and healthy. The Dockerfile follows best practices (multi-stage, non-root, layer caching). However, there is a **CRITICAL SQL injection** in `prestart.sh`, the **CI/CD pipeline is not automated**, the **worker container lacks a healthcheck**, and **no resource limits** are defined for any service. - ---- - -## 1. Container Health — docker-compose.yml - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 1.1 | **HIGH** | Worker container (`crm-worker`) has NO healthcheck defined | `docker-compose.yml:119-153` | -| 1.2 | **MEDIUM** | No resource limits (memory/CPU) on ANY service | `docker-compose.yml` (entire file) | -| 1.3 | LOW | PostgreSQL, Redis, and app all use `restart: unless-stopped` ✓ | `docker-compose.yml:14,48,73,126` | -| 1.4 | LOW | `depends_on` with `condition: service_healthy` correctly used ✓ | `docker-compose.yml:75-76,130-131` | - -### Details - -**1.1 — Worker missing healthcheck:** -The `crm-worker` service has no `healthcheck` key. The `healthcheck.sh` script supports worker mode (Redis ping fallback), but it is never invoked for the worker container. Docker/Coolify cannot detect a wedged worker. - -**Recommended fix:** -```yaml -crm-worker: - healthcheck: - test: ["CMD", "/app/healthcheck.sh"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 15s -``` - -**1.2 — No resource limits:** -None of the 4 services define `deploy.resources.limits` or `mem_limit`/`cpus`. A memory leak in the app or worker can OOM the host. In Coolify deployments, resource limits should be set via Coolify resource constraints. - ---- - -## 2. Worker Stability — worker.sh, app/core/worker.py - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 2.1 | **MEDIUM** | No ARQ job retry configuration (`max_tries` not set) | `app/core/worker.py:243-244` | -| 2.2 | LOW | `max_jobs = 10`, `job_timeout = 300s` — reasonable defaults | `app/core/worker.py:243-244` | -| 2.3 | LOW | Distributed cron lock via Redis SET NX + Lua release ✓ | `app/core/worker.py:30-52` | -| 2.4 | LOW | `on_startup` properly initializes plugins, event bus, search providers ✓ | `app/core/worker.py:78-130` | -| 2.5 | LOW | `exec arq` in worker.sh makes ARQ PID 1 for signal forwarding ✓ | `worker.sh:20` | - -### Details - -**2.1 — No job retry:** -ARQ's `WorkerSettings` does not set `max_tries`. ARQ defaults to `max_tries=0` (no retries). A transient failure (DB timeout, Redis blip) will permanently fail the job. For critical jobs like `process_outbox`, this can cause permanent outbox stalls. - -**Recommended fix:** -```python -class WorkerSettings: - max_tries = 3 # Retry failed jobs up to 3 times -``` - ---- - -## 3. Redis Connections — app/core/redis.py, app/core/auth.py, app/core/worker.py - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 3.1 | **MEDIUM** | Cron lock helpers create a NEW Redis client per acquire/release — no pooling | `app/core/worker.py:37,49` | -| 3.2 | **MEDIUM** | No Redis connection pool size configured — uses redis-py defaults | `app/core/auth.py:37-38,62-63` | -| 3.3 | LOW | Session keys use SETEX with TTL (28800s = 8h) ✓ | `app/core/auth.py:145-147` | -| 3.4 | LOW | Global singleton pattern prevents connection leaks for app/API Redis ✓ | `app/core/auth.py:28-38` | - -### Details - -**3.1 — Cron lock connection churn:** -`_acquire_cron_lock()` and `_release_cron_lock()` each call `aioredis.from_url()` and `aclose()` on every invocation. With outbox processing running every 5 seconds, this creates 24 Redis connections/minute per cron job just for lock management. - -**Recommended fix:** Reuse the global Redis client from `get_redis()` or pass the connection via ARQ context (`ctx['redis']`). - -**3.2 — No pool size:** -`aioredis.from_url()` is called without `max_connections` parameter. Under high load, the default pool may exhaust. Add: -```python -aioredis.from_url(url, decode_responses=True, max_connections=50) -``` - ---- - -## 4. Migration Pipeline — alembic/ - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 4.1 | LOW | 83 migrations, single head at 0083 ✓ | `alembic/versions/` | -| 4.2 | LOW | 0028_rls_force and 0028_user_preferences are properly chained (not branched) ✓ | `alembic/versions/0028_*.py` | -| 4.3 | LOW | test_migrations.sh tests upgrade/downgrade/idempotency ✓ | `scripts/test_migrations.sh` | -| 4.4 | LOW | Downgrade failure is non-fatal in test_migrations.sh (acceptable) | `scripts/test_migrations.sh:54` | -| 4.5 | LOW | alembic/env.py uses async engine from config ✓ | `alembic/env.py:35-44` | - -### Details - -Migration graph is clean — `alembic heads` confirms a single head. The test script (`test_migrations.sh`) creates a throwaway database, runs `upgrade head`, verifies table count ≥ 50, runs `downgrade base`, then re-upgrades to verify idempotency. Solid approach. - ---- - -## 5. CI/CD Pipeline — .github/workflows/, scripts/ - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 5.1 | **HIGH** | Only 1 GitHub workflow exists (cross-plugin imports only) — no test/build/deploy automation | `.github/workflows/check-cross-plugin-imports.yml` | -| 5.2 | **HIGH** | `scripts/ci_pipeline.sh` has 15 quality gates but is NOT wired into any CI workflow | `scripts/ci_pipeline.sh` (entire file) | -| 5.3 | LOW | Cross-plugin import check is well-implemented with exemptions ✓ | `scripts/check_cross_plugin_imports.py` | -| 5.4 | LOW | CI pipeline includes SQL injection, Jinja2 sandbox, RLS, and fail-closed checks ✓ | `scripts/ci_pipeline.sh:52-62` | - -### Details - -**5.1 + 5.2 — CI pipeline not automated:** -The `.github/workflows/` directory contains only `check-cross-plugin-imports.yml` (triggers on `app/plugins/**` changes). The comprehensive `ci_pipeline.sh` with 15 checks (compile, imports, alembic, TypeScript, frontend build, test collection, SQL injection, Jinja2 sandbox, RLS, fail-closed, ruff, cross-tenant, dependency scan, container smoke, npm ci) is **never executed in CI**. It must be run manually. - -**Recommended fix:** Create `.github/workflows/ci.yml`: -```yaml -name: CI -on: [push, pull_request] -jobs: - ci: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: { python-version: '3.12' } - - uses: actions/setup-node@v4 - with: { node-version: '20' } - - run: pip install -r requirements.txt - - run: cd frontend && npm ci - - run: bash scripts/ci_pipeline.sh -``` - ---- - -## 6. Prestart Script — prestart.sh - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 6.1 | **CRITICAL** | SQL injection: password interpolated into SQL via f-string without escaping | `prestart.sh:49` | -| 6.2 | LOW | Uses `set -e` for fail-fast ✓ | `prestart.sh:14` | -| 6.3 | LOW | `exec uvicorn` makes it PID 1 for signal forwarding ✓ | `prestart.sh:62` | -| 6.4 | LOW | Uses MIGRATION_DATABASE_URL for alembic (RLS bypass for DDL) ✓ | `prestart.sh:18` | - -### Details - -**6.1 — SQL injection in prestart.sh:** -Line 49: -```python -await conn.execute(text( - f"ALTER ROLE crm_runtime WITH LOGIN PASSWORD '{pwd}' NOSUPERUSER NOBYPASSRLS" -)) -``` -The `RUNTIME_DB_PASSWORD` environment variable is interpolated directly into a SQL string using an f-string. If the password contains a single quote (`'`), the SQL will break or be exploitable. This is a **CRITICAL** injection vulnerability. - -**Recommended fix:** Use parameterized query or escape the password: -```python -await conn.execute(text( - "ALTER ROLE crm_runtime WITH LOGIN PASSWORD :pwd NOSUPERUSER NOBYPASSRLS" -), {"pwd": pwd}) -``` - ---- - -## 7. Dockerfile - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 7.1 | LOW | Multi-stage build (3 stages: frontend, builder, runtime) ✓ | `Dockerfile:5,18,39` | -| 7.2 | LOW | Non-root user (appuser, UID 1000, GID 1000) ✓ | `Dockerfile:60-61` | -| 7.3 | LOW | Layer caching for npm (`COPY package.json` before `COPY frontend/`) ✓ | `Dockerfile:10-12` | -| 7.4 | LOW | Layer caching for pip (`COPY requirements.txt` before app source) ✓ | `Dockerfile:30-31` | -| 7.5 | LOW | `.dockerignore` excludes secrets, tests, docs, `.git` ✓ | `.dockerignore` | -| 7.6 | LOW | HEALTHCHECK defined in Dockerfile ✓ | `Dockerfile:73-74` | -| 7.7 | LOW | `apt-get` cleanup with `rm -rf /var/lib/apt/lists/*` ✓ | `Dockerfile:23,57` | - -**No issues found.** The Dockerfile follows best practices. - ---- - -## 8. Environment Configuration — .env.example, .env.docker.example - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 8.1 | **MEDIUM** | `.env.example` missing `MIGRATION_DATABASE_URL`, `REDIS_PASSWORD`, `RUNTIME_DB_PASSWORD` | `.env.example` | -| 8.2 | LOW | `.env.docker.example` is comprehensive with all required vars ✓ | `.env.docker.example` | -| 8.3 | LOW | Required vars enforced with `:?` in docker-compose.yml (POSTGRES_PASSWORD, REDIS_PASSWORD, SECRET_KEY, DATABASE_URL) ✓ | `docker-compose.yml:17,50,83,84` | -| 8.4 | LOW | Secret generation instructions included ✓ | `.env.docker.example:16-17,24-25` | - -### Details - -**8.1 — `.env.example` incomplete:** -The `.env.example` file (used for local dev) is missing `MIGRATION_DATABASE_URL`, `REDIS_PASSWORD`, and `RUNTIME_DB_PASSWORD`. Developers following `.env.example` will hit runtime errors when the prestart script tries to set the crm_runtime password or when alembic needs the migration URL. - ---- - -## 9. Health Check — healthcheck.sh, app/routes/health.py - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 9.1 | LOW | `/api/v1/health` returns 200 `healthy` on live deployment ✓ | `https://crm.media-on.de/api/v1/health` | -| 9.2 | LOW | Three-tier health endpoints: `/health/live`, `/health/ready`, `/api/v1/health` ✓ | `app/routes/health.py:22,34,57` | -| 9.3 | LOW | healthcheck.sh dual-mode (HTTP + Redis fallback) ✓ | `healthcheck.sh:5-19` | -| 9.4 | LOW | Readiness probe checks DB, Redis, storage, worker heartbeat ✓ | `app/routes/health.py:37-52` | -| 9.5 | LOW | Redis password in healthcheck command visible in `docker inspect` (low risk — internal network) | `docker-compose.yml:59` | - -**No critical issues.** Health check implementation is solid. - ---- - -## 10. Backup/Restore — app/services/backup_service.py, scripts/backup.py, scripts/restore.py - -### Findings - -| # | Severity | Finding | Location | -|---|----------|---------|----------| -| 10.1 | **MEDIUM** | `backup_service.py` stores backups in `/tmp/leocrm-backups` — ephemeral, lost on container restart | `app/services/backup_service.py:13` | -| 10.2 | **MEDIUM** | `restore_backup()` uses `--clean --if-exists` but no transaction wrapping — partial restore possible | `app/services/backup_service.py:118-125` | -| 10.3 | LOW | `scripts/backup.py` is more robust: manifest, retention, S3/Nextcloud support ✓ | `scripts/backup.py` (entire) | -| 10.4 | LOW | `scripts/restore.py` validates manifest.json before restore ✓ | `scripts/restore.py:93-98` | -| 10.5 | LOW | `test_backup_restore.py` has basic unit tests for params/manifest ✓ | `tests/test_backup_restore.py` | -| 10.6 | LOW | No scheduled backup automation — must be triggered manually | (no cron/scheduler for backups) | - -### Details - -**10.1 — Ephemeral backup storage:** -`backup_service.py` uses `BACKUP_DIR = Path("/tmp/leocrm-backups")`. In a Docker container, `/tmp` is ephemeral. If the container restarts, all backups are lost. The volume mount in docker-compose only covers `/data/storage`, not `/tmp`. - -**Recommended fix:** Change to `/data/backups` or use the `storage` volume. - -**10.2 — Non-atomic restore:** -`restore_backup()` runs `pg_restore --clean --if-exists --no-owner --no-acl` without wrapping in a transaction. If the restore fails midway, the database is left in a partially-restored state with no automatic rollback. - ---- - -## Summary of Recommendations (Priority Order) - -1. **CRITICAL** — Fix SQL injection in `prestart.sh:49` — use parameterized query -2. **HIGH** — Add healthcheck to `crm-worker` in `docker-compose.yml` -3. **HIGH** — Wire `scripts/ci_pipeline.sh` into a GitHub/Forgejo workflow -4. **HIGH** — Expand `.github/workflows/` to include test/build/lint gates -5. **MEDIUM** — Add `max_tries=3` to `WorkerSettings` for job retry -6. **MEDIUM** — Add resource limits to all services in `docker-compose.yml` -7. **MEDIUM** — Reuse Redis connection in cron lock helpers instead of creating new clients -8. **MEDIUM** — Change `backup_service.py` backup dir from `/tmp` to persistent volume -9. **MEDIUM** — Add `MIGRATION_DATABASE_URL`, `REDIS_PASSWORD`, `RUNTIME_DB_PASSWORD` to `.env.example` -10. **LOW** — Configure Redis `max_connections` in `auth.py` -11. **LOW** — Add scheduled backup cron job -12. **LOW** — Wrap `restore_backup()` in a transaction - ---- - -## Live Deployment Status - -| Check | Result | -|-------|--------| -| Health endpoint | ✅ `{"status":"healthy","version":"1.0.0"}` | -| HTTPS | ✅ Reachable | -| Response time | < 3s | - diff --git a/docs/migration_history_audit.md b/docs/migration_history_audit.md deleted file mode 100644 index 89bf53c..0000000 --- a/docs/migration_history_audit.md +++ /dev/null @@ -1,91 +0,0 @@ -# Migration History Audit - -**Erstellt:** 2026-08-03 -**Alembic-Head:** 0092 -**Produktions-Stand:** 0092 - ---- - -## Bestätigte Schema-Diskrepanzen - -### 1. files.size_bytes — Typ-Diskrepanz - -| Quelle | Typ | -|--------|-----| -| Alembic 0071 | INTEGER | -| DMS Plugin Migration 0001 | BIGINT | -| SQLAlchemy Model | Integer | -| **Produktion** | **bigint** | - -**Klassifizierung:** Echte Schemaänderung -**Forward-Migration:** 0093 — `ALTER COLUMN size_bytes TYPE BIGINT` - -### 2. GIN-Indizes — Fehlendes USING GIN - -Alembic 0002 erstellt: -```sql -CREATE INDEX ix_companies_search_vec ON companies (search_tsv) -``` - -Produktion hat: -```sql -CREATE INDEX ix_companies_search_vec ON companies USING gin (search_tsv) -``` - -Betroffene Tabellen/Indizes (in Produktion als GIN vorhanden): -- contacts.ix_contacts_search_tsv -- audit_log.ix_audit_log_search_tsv -- calendar_entries.ix_cal_entries_search_tsv -- comm_messages.ix_comm_messages_search_tsv -- files.ix_files_content_tsv -- mails.ix_mails_body_tsv -- tags.ix_tags_search_tsv - -**Klassifizierung:** Echte Schemaänderung (Index-Typ) -**Forward-Migration:** 0094 — GIN-Indizes neu erstellen mit USING GIN - -### 3. guest_users — Fehlender UNIQUE Constraint - -Alembic 0059 erstellt: -```sql -CREATE INDEX ix_guest_users_email_tenant ON guest_users (email, tenant_id) -``` - -Model und Produktion haben: -```sql -CREATE UNIQUE INDEX ix_guest_users_email_tenant ON guest_users (email, tenant_id) -``` - -**Klassifizierung:** Echte Schemaänderung (Unique fehlt in Alembic) -**Forward-Migration:** 0095 — Index als UNIQUE neu erstellen - -### 4. plugins.name — Doppelter Unique-Index - -Produktion hat zwei UNIQUE-Indizes auf plugins.name: -- `plugins_name_key` (von `unique=True` in Column-Definition) -- `ix_plugins_name` (von explizitem `CREATE INDEX` in 0003, als UNIQUE in Produktion) - -Alembic 0003 erstellt `ix_plugins_name` ohne `UNIQUE`, aber Column hat `unique=True`. - -**Klassifizierung:** Nur Idempotenzänderung (Redundanz) -**Forward-Migration:** 0094 — Doppelten Index entfernen - ---- - -## Keine Diskrepanz gefunden - -- tenants.slug: unique=True in 0001 + Model + Produktion → ✅ -- plugin_migrations: UniqueConstraint in 0003 + Model + Produktion → ✅ -- RLS-Policies: Alle korrekt in Produktion → ✅ -- Workspace-Tabellen: RLS fail-closed, Tabellen korrekt → ✅ - ---- - -## Forward-Migration-Plan - -| Migration | Inhalt | -|-----------|--------| -| 0093 | files.size_bytes INTEGER → BIGINT | -| 0094 | GIN-Indizes reparieren + plugins.name doppelten Index entfernen | -| 0095 | guest_users email+tenant_id UNIQUE INDEX | -| 0096 | Workspace tenant_integrity (Plan 4.3) | diff --git a/docs/phase0_error_list.md b/docs/phase0_error_list.md deleted file mode 100644 index d349733..0000000 --- a/docs/phase0_error_list.md +++ /dev/null @@ -1,162 +0,0 @@ -# Phase 0 — Frozen Error List (P0/P1) - -**Date:** 2026-07-31 -**Baseline commit:** 11d6faa (tag: v-phase0-baseline) -**Phase 0 commit:** 032a7e8 - ---- - -## P0 — Critical Security Issues - -### P0-01: All tables owned by SUPERUSER role -- **Severity:** P0 -- **Files:** All 123 tables in `public` schema -- **Tables:** ALL -- **Reproduction:** `SELECT tableowner FROM pg_tables WHERE schemaname='public'` → all `crm_user` -- **Target:** Owner = `crm_migration` (NOSUPERUSER, NOBYPASSRLS) -- **Status:** Open — Phase 1 - -### P0-02: `crm_migration` has BYPASSRLS -- **Severity:** P0 -- **Files:** DB role `crm_migration` -- **Reproduction:** `SELECT rolbypassrls FROM pg_roles WHERE rolname='crm_migration'` → `true` -- **Target:** `ALTER ROLE crm_migration NOBYPASSRLS` -- **Status:** Open — Phase 1 - -### P0-03: RLS disabled on ~70+ tenant tables -- **Severity:** P0 -- **Tables:** contacts, addresses, attachments, ai_*, calendar_*, comm_*, mail_*, workflows, etc. -- **Reproduction:** `SELECT relname FROM pg_class WHERE relrowsecurity=false AND relforcerowsecurity=true` -- **Target:** ENABLE ROW LEVEL SECURITY on all tenant tables -- **Status:** Open — Phase 1 - -### P0-04: Old RLS policies scoped to `{public}` — potential cross-transaction leak -- **Severity:** P0 -- **Tables:** ~70+ tables with old `tenant_isolation` policy -- **Reproduction:** `SELECT policyname, roles FROM pg_policies WHERE roles='{public}'` -- **Target:** Drop old policies, create new ones scoped to `{crm_api, crm_worker}` -- **Status:** Open — Phase 1 - -### P0-05: No separate database connections for auth/api/worker/migration -- **Severity:** P0 -- **Files:** `app/config.py`, `app/core/db/__init__.py` -- **Reproduction:** `grep -n 'auth_database_url\|worker_database_url' app/config.py` → not found -- **Target:** 4 separate engines with separate pools and roles -- **Status:** Open — Phase 1 - -### P0-06: Worker uses `crm_api` role instead of `crm_worker` -- **Severity:** P0 -- **Files:** `docker-compose.yml` worker environment -- **Reproduction:** `docker exec leocrm-worker env | grep DATABASE_URL` → `crm_api` -- **Target:** Worker uses `crm_worker` role -- **Status:** Open — Phase 1 - -### P0-07: `crm_runtime` legacy role with full CRUD on ALL tables -- **Severity:** P0 -- **Files:** DB role `crm_runtime` -- **Reproduction:** `SELECT count(*) FROM information_schema.role_table_grants WHERE grantee='crm_runtime'` → 492 -- **Target:** Remove role or revoke all grants -- **Status:** Open — Phase 1 - -### P0-08: `crm_api` and `crm_worker` have access to `alembic_version` -- **Severity:** P0 -- **Tables:** `alembic_version` -- **Reproduction:** `SELECT * FROM information_schema.role_table_grants WHERE table_name='alembic_version' AND grantee IN ('crm_api','crm_worker')` -- **Target:** Revoke access — only `crm_migration` should access alembic_version -- **Status:** Open — Phase 1 - -### P0-09: `crm_auth` missing `password_reset_tokens` access -- **Severity:** P0 -- **Tables:** `password_reset_tokens` -- **Reproduction:** `SELECT * FROM information_schema.role_table_grants WHERE grantee='crm_auth' AND table_name='password_reset_tokens'` → empty -- **Target:** Grant SELECT, INSERT, UPDATE on `password_reset_tokens` to `crm_auth` -- **Status:** Open — Phase 1 - -### P0-10: `crm_auth` has access to `groups`, `roles`, `user_groups` — too broad -- **Severity:** P0 -- **Tables:** `groups`, `roles`, `user_groups` -- **Reproduction:** `SELECT table_name FROM information_schema.role_table_grants WHERE grantee='crm_auth'` -- **Target:** Revoke — auth only needs users, user_tenants, tenants, password_reset_tokens -- **Status:** Open — Phase 1 - -## P1 — High Priority Issues - -### P1-01: `app.tenant_id` legacy variable still set -- **Severity:** P1 -- **Files:** `app/core/db/__init__.py:128` (now fixed) -- **Reproduction:** `grep -rn 'app.tenant_id' app/ --include='*.py'` (was setting both vars) -- **Target:** Only `app.current_tenant_id` — FIXED in Phase 0 -- **Status:** ✅ Fixed - -### P1-02: Cross-plugin import in report_generator -- **Severity:** P1 -- **Files:** `app/plugins/builtins/report_generator/jobs.py:79` -- **Reproduction:** `grep 'from app.plugins.builtins.dms' app/plugins/builtins/report_generator/jobs.py` -- **Target:** Use DmsContract via contract registry — FIXED in Phase 0 -- **Status:** ✅ Fixed - -### P1-03: `test_cross_tenant_security_v2.py` was deleted (contained `§§include()`) -- **Severity:** P1 -- **Files:** `tests/test_cross_tenant_security_v2.py` -- **Reproduction:** File did not exist -- **Target:** Recreate with real RLS tests using unprivileged role — FIXED in Phase 0 -- **Status:** ✅ Fixed - -### P1-04: Existing tests reference `app.tenant_id` in assertions -- **Severity:** P1 -- **Files:** `tests/test_cross_tenant_security.py`, `tests/test_cross_tenant_standalone.py` -- **Reproduction:** `grep 'app.tenant_id' tests/test_cross_tenant*.py` -- **Target:** Only test `app.current_tenant_id` — FIXED in Phase 0 -- **Status:** ✅ Fixed - -### P1-05: No `crm_platform_admin` role defined -- **Severity:** P1 -- **Files:** DB roles -- **Reproduction:** `SELECT * FROM pg_roles WHERE rolname='crm_platform_admin'` → not found -- **Target:** Create role for one-time infrastructure setup -- **Status:** Open — Phase 1 - -### P1-06: No Default Privileges set for future tables -- **Severity:** P1 -- **Files:** DB configuration -- **Reproduction:** `SELECT * FROM pg_default_privileges WHERE defaclrole='crm_migration'` → empty -- **Target:** Set default privileges for `crm_migration` owner -- **Status:** Open — Phase 1 - -### P1-07: Login path uses same DB connection as API -- **Severity:** P1 -- **Files:** `app/routes/auth.py`, `app/core/db/__init__.py` -- **Reproduction:** Login endpoint uses `get_db()` (crm_api engine) -- **Target:** Login uses `get_auth_db()` (crm_auth engine) -- **Status:** Open — Phase 1 - -### P1-08: Startup code accesses tenant tables without tenant context -- **Severity:** P1 -- **Files:** `app/main.py:169-231` -- **Reproduction:** Plugin activation during startup may access tenant tables -- **Target:** Per-tenant context for tenant operations -- **Status:** Open — Phase 1 - -### P1-09: No RLS coverage check automation -- **Severity:** P1 -- **Files:** None — needs creation -- **Target:** Automated test/script checking all tenant tables for RLS -- **Status:** Open — Phase 1 - -### P1-10: `crm_worker` has full CRUD on ALL tables including global tables -- **Severity:** P1 -- **Tables:** users, tenants, user_tenants, sessions, plugins, etc. -- **Reproduction:** `SELECT count(*) FROM information_schema.role_table_grants WHERE grantee='crm_worker'` → 492 -- **Target:** Narrow to only necessary job/outbox/tenant tables -- **Status:** Open — Phase 1 - ---- - -## Summary - -| Status | Count | -|--------|-------| -| Open (P0) | 10 | -| Open (P1) | 7 | -| Fixed (P1) | 4 | -| Total | 21 | diff --git a/docs/phase0_phase1_acceptance_report.md b/docs/phase0_phase1_acceptance_report.md deleted file mode 100644 index 8f3a914..0000000 --- a/docs/phase0_phase1_acceptance_report.md +++ /dev/null @@ -1,410 +0,0 @@ -ÜBERHOLT – NICHT ALS UMSETZUNGSANWEISUNG VERWENDEN -# Phase 0 + Phase 1 — Abschluss-Abnahmeprotokoll - -**Stand:** 2026-07-31 12:06 CEST -**Git-Commit:** 3032ad2 (main) -**Alembic-Head:** 0088 -**Docker-Image:** dx4pqdziu4uj6x9fxs1u5z0x:3032ad2 (Coolify-Build aus Git) - ---- - -## Container-Status - -| Container | Status | Rolle | -|-----------|--------|------| -| dx4pqdziu4uj6x9fxs1u5z0x-100457116674 | Up, healthy | API (crm_api) | -| leocrm-worker | Up, healthy | Worker (crm_worker) | -| crm-postgres | Up | PostgreSQL | -| crm-redis | Up | Redis | - -## Datenbankrollen und Verbindungen - -| Rolle | Verbindung | Eigenschaften | -|-------|-----------|--------------| -| crm_platform_admin | — | NOSUPERUSER, NOBYPASSRLS, NOLOGIN | -| crm_migration | MIGRATION_DATABASE_URL | NOSUPERUSER, BYPASSRLS, Tabellenowner | -| crm_auth | AUTH_DATABASE_URL | NOSUPERUSER, NOBYPASSRLS | -| crm_api | DATABASE_URL | NOSUPERUSER, NOBYPASSRLS | -| crm_worker | WORKER_DATABASE_URL | NOSUPERUSER, NOBYPASSRLS | - -Verifiziert via `docker exec env | grep DATABASE`: -- API: DATABASE_URL=crm_api, AUTH_DATABASE_URL=crm_auth ✅ -- Worker: DATABASE_URL=crm_worker, WORKER_DATABASE_URL=crm_worker ✅ -- Migration: MIGRATION_DATABASE_URL=crm_migration ✅ - ---- - -## Gate 1 — Reproduzierbares Coolify-Deployment ✅ - -### Durchführung -1. Alle Änderungen auf main gepusht (Commit 3032ad2) ✅ -2. Coolify-Rebuild aus Git getriggert ✅ -3. Docker-Image ausschließlich aus Repository gebaut ✅ -4. Keine manuellen Dateiänderungen im laufenden Container ✅ -5. API- und Worker-Container vollständig neu erstellt ✅ -6. Migrationen automatisch bis Alembic-Head 0088 ausgeführt ✅ - -### Nachweis nach dem Deployment -- API healthy ✅ -- Worker healthy ✅ -- PostgreSQL healthy ✅ -- Redis healthy ✅ -- Login erfolgreich ✅ -- API verwendet crm_api ✅ -- Authentifizierung verwendet crm_auth ✅ -- Worker verwendet crm_worker ✅ -- Migrationen verwenden crm_migration ✅ -- Alembic-Head ist 0088 ✅ -- RLS-Tests: 0 rows ohne Kontext, 8 rows mit Kontext ✅ -- Worker verarbeitet Outbox-Jobs ✅ - -### Dockerfile-Fixes -- `npm ci --silent 2>/dev/null || npm install --silent` → `npm ci --legacy-peer-deps || npm install --legacy-peer-deps` (vite 8 / @vitejs/plugin-react 4.7.0 peer dependency conflict) - ---- - -## Gate 2 — Neuinstallation auf leerer Datenbank ⏳ OFFEN - -Nicht durchgeführt — erfordert separate Testumgebung in Coolify mit eigener PostgreSQL-Instanz. - ---- - -## Gate 3 — Vollständiger Restore-Test ⏳ OFFEN - -Nicht durchgeführt — erfordert separate Testdatenbank und DMS-Storage. - ---- - -## Gate 4 — Passwort-Reset end-to-end ✅ - -### Durchführung -1. Reset angefordert: `POST /api/v1/auth/password-reset/request` → 200 OK ✅ -2. Token in DB generiert (hash, nicht raw) ✅ -3. ARQ-Mailjob erzeugt und verarbeitet (Worker-Log: `send_password_reset_email ●`) ✅ -4. SMTP-Versand über mail.media-on.de:465 (implicit TLS) ✅ -5. Email im Postfach admin@media-on.de angekommen (IMAP verifiziert) ✅ -6. Reset-Link aus Email extrahiert ✅ -7. Passwort erfolgreich geändert: `POST /api/v1/auth/password-reset/confirm` → 200 OK ✅ -8. Login mit altem Passwort fehlschlägt: 401 `invalid_credentials` ✅ -9. Login mit neuem Passwort funktioniert: 200 OK mit user_id, csrf_token ✅ -10. Token-Wiederverwendung fehlschlägt: 400 `invalid_token` ✅ -11. Unbekannte Email: 200 OK ohne Benutzerexistenz-Offenlegung ✅ -12. Reset-Token in Logs: Nicht gefunden (kein Token-Leak) ✅ -13. Passwort auf Admin123! zurückgesetzt und Login verifiziert ✅ - -### SMTP-Konfiguration -- SMTP_HOST=mail.media-on.de -- SMTP_PORT=465 (implicit TLS) -- SMTP_USER=test@media-on.de -- SMTP_FROM_EMAIL=admin@media-on.de -- SMTP_USE_TLS=true - -### Code-Fixes -- `app/core/worker.py`: `app.core.jobs` zur plugin_job_modules Liste hinzugefügt (Worker fand `send_password_reset_email` nicht) -- `app/core/jobs.py`: SMTP `start_tls` → `use_tls` für Port 465 (implicit TLS) -- `app/services/auth_service.py`: Audit-Log über separate API-Session (crm_api) mit Tenant-Kontext -- `alembic/versions/0088_auth_rls_policies.py`: RLS-Policies für crm_auth auf password_reset_tokens und audit_log - -### Migration 0088 -- `password_reset_tokens`: crm_auth SELECT (lookup), UPDATE (mark used), INSERT (create token with tenant context) -- `password_reset_tokens`: crm_api/crm_worker tenant isolation -- `audit_log`: crm_auth INSERT with tenant context -- `users`: crm_auth UPDATE (password hash update) -- Alle Grants über Migration, nicht manuell - -### Reset-URL -- Aktuell: `http://localhost:5173/reset-password?token=...` (FRONTEND_URL Default) -- Fix: FRONTEND_URL=https://crm.media-on.de in Coolify .env gesetzt -- Bei nächstem Rebuild werden Reset-Links korrekt auf https://crm.media-on.de zeigen - ---- - -## Gate 5 — Worker und Eventhandler ⏳ OFFEN - -Worker verarbeitet Outbox-Jobs und send_password_reset_email. Plugin-Eventhandler-Registrierung ist noch nicht vollständig implementiert. - ---- - -## RLS-Verifikation - -| Test | Ergebnis | -|------|----------| -| crm_api SELECT ohne Kontext | 0 rows ✅ | -| crm_api SELECT mit Kontext | 8 rows ✅ | -| Cross-Tenant INSERT | ERROR: violates RLS ✅ | -| Cross-Tenant UPDATE | UPDATE 0 ✅ | -| Cross-Tenant DELETE | DELETE 0 ✅ | -| WITH CHECK violation | ERROR: WITH CHECK ✅ | -| crm_migration BYPASSRLS | 7 rows tenantübergreifend ✅ | - ---- - -## Alle 15 Abnahmekriterien - -| # | Kriterium | Status | -|---|-----------|--------| -| 1 | Login über crm_auth | ✅ | -| 2 | API über crm_api | ✅ | -| 3 | crm_api NOSUPERUSER/NOBYPASSRLS | ✅ | -| 4 | crm_worker NOSUPERUSER/NOBYPASSRLS | ✅ | -| 5 | Cross-Tenant Read blockiert | ✅ | -| 6 | Cross-Tenant Write blockiert | ✅ | -| 7 | Kein Fachdaten ohne Kontext | ✅ | -| 8 | Tenantwechsel prüft Membership | ✅ | -| 9 | Passwort-Reset funktioniert | ✅ | -| 10 | Startup ohne Bootstrap-Policy | ✅ | -| 11 | Per-Tenant Startup | ✅ | -| 12 | Migration auf bestehender DB | ✅ | -| 13 | RLS-Abdeckungsprüfung | ✅ | -| 14 | app.tenant_id entfernt | ✅ | -| 15 | Getrennte DB-Rollen | ✅ | - ---- - -## Offene Risiken - -1. **Gate 2 (leere DB-Neuinstallation):** Nicht durchgeführt — erfordert separate Testumgebung -2. **Gate 3 (Restore-Test):** Nicht durchgeführt — erfordert separate Testdatenbank -3. **Gate 5 (Worker-Eventhandler):** Plugin-Eventhandler-Registrierung nicht vollständig -4. **FRONTEND_URL:** Wird erst bei nächstem Coolify-Rebuild wirksam (aktuell noch localhost:5173 in Emails) -5. **Worker-Container:** Wird nicht über Coolify verwaltet (manuell mit docker run erstellt) — bei Coolify-Rebuild wird der Worker nicht automatisch neu erstellt -6. **SMTP_FROM_EMAIL:** Verwendet admin@media-on.de als Absender (noreply@media-on.de existiert nicht auf dem Mail-Server) - ---- - -## Rollback-Verfahren - -1. `pg_restore` aus Forgejo-Release-Backup -2. `alembic downgrade 0087` (Migration 0088 rückgängig machen) -3. `git reset --hard v-phase0-baseline` -4. Coolify-Rebuild aus altem Commit - ---- - -## Freigabestatus - -**BEDINGT ABGENOMMEN** - -- Gate 1 (Coolify-Deployment): ✅ Bestanden -- Gate 4 (Passwort-Reset): ✅ Bestanden -- Gate 2 (leere DB): ⏳ Offen -- Gate 3 (Restore): ⏳ Offen -- Gate 5 (Worker-Eventhandler): ⏳ Offen - -Phase 0 und Phase 1 können als technisch abgenommen gelten, sobald Gate 2, 3 und 5 abgeschlossen sind. - ---- - -## Gate 2 — Neuinstallation auf leerer Datenbank ✅ BESTANDEN - -**Datum:** 2026-07-31 -**Git-Commit:** 89b775b -**Test-Service:** g13zwdav6myvpnop96dj7tpx (crmtest.media-on.de) -**Image:** dx4pqdziu4uj6x9fxs1u5z0x:89b775b -**DB-Image:** pgvector/pgvector:pg16 - -### Durchführung - -1. Coolify Test-Service mit eigener PostgreSQL, Redis, API, Worker erstellt -2. DB-Volume gelöscht für vollständig leere DB -3. Image aus Git-Commit 89b775b auf Server gebaut -4. Compose aktualisiert: Image 89b775b + pgvector/pgvector:pg16 -5. `docker compose up -d` — alle Container gestartet -6. prestart.sh führte `alembic upgrade head` als crm_user aus -7. Migrationen 0001→0090 automatisch ausgeführt -8. Plugin-Migrationen über crm_migration ausgeführt (P0-Fix) -9. seed_admin.py ausgeführt — Tenant + Role + User + UserTenant erstellt -10. Login über HTTPS getestet - -### Verifikationsergebnisse - -| Kriterium | Ergebnis | -|-----------|----------| -| Coolify-Deployment erfolgreich | ✅ Alle 4 Container healthy | -| API healthy | ✅ Up 2 minutes (healthy) | -| Worker healthy | ✅ Up 2 minutes (healthy) | -| PostgreSQL healthy | ✅ Up 2 minutes (healthy) | -| Redis healthy | ✅ Up 2 minutes | -| Alembic-Head | ✅ 0090 | -| Tabellen erstellt | ✅ 124 Tabellen | -| Keine manuellen Schemaänderungen | ✅ Ausschließlich Migrationen | -| Rollen vorhanden | ✅ crm_migration (BYPASSRLS), crm_api/crm_auth/crm_worker (NOBYPASSRLS, NOSUPERUSER) | -| RLS aktiviert | ✅ 47 Tabellen mit RLS | -| Legacy app.tenant_id Policies | ✅ 0 (Migration 0090 fixt _old Tabellen) | -| Admin erfolgreich angelegt | ✅ Tenant + Role + User + UserTenant | -| Login erfolgreich | ✅ 200 OK mit user_id, csrf_token, tenant_id | -| RLS ohne Kontext fail-closed | ✅ 0 rows | -| Cross-Tenant INSERT blockiert | ✅ 'new row violates row-level security policy' | -| Valid INSERT funktioniert | ✅ INSERT 0 1 | -| crm_api DDL blockiert | ✅ 'permission denied for schema public' | - -### Ausgeführte Befehle - -``` -# Image bauen -git clone https://forgejo.media-on.de/Leopoldadmin/leocrm.git -git checkout 89b775b -docker build -t dx4pqdziu4uj6x9fxs1u5z0x:89b775b . - -# Compose aktualisieren und neu starten -docker compose up -d - -# Verifikation -psql -U crm_user -d crm_test_db -f gate2_verify.sql -psql -U crm_user -d crm_test_db -f gate2_rls.sql -psql -U crm_user -d crm_test_db -f gate2_columns.sql - -# Seed -docker exec api-g13zwdav6myvpnop96dj7tpx python3 scripts/seed_admin.py - -# Login -curl -X POST https://crmtest.media-on.de/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -H "Origin: https://crmtest.media-on.de" \ - -d '{"email":"admin@media-on.de","password":"Admin123!"}' -``` - -### Bekannte Issues - -1. **Login-Rolle 'viewer' statt 'admin':** seed_admin.py erstellt Role mit name='admin' und permissions={'*:*': True}, aber Login-Response gibt role='viewer'. Vermutlich wird die Rolle aus UserTenant.role_id nicht korrekt aufgelöst. Kein Gate-2-Blocker — RLS und Tenant-Isolation funktionieren korrekt. -2. **pgvector-Extension:** Test-DB verwendet pgvector/pgvector:pg16 statt postgres:16-alpine. Produktion verwendet ebenfalls pgvector. Compose-Datei des Test-Services muss in Coolify aktualisiert werden. - -### Gate-2-Abnahme: BESTANDEN - -Alle Abnahmekriterien erfüllt. Die Anwendung startet auf einer vollständig leeren Datenbank ohne manuelle Nacharbeit. - ---- - -## Gate 5 — Worker und Eventhandler ✅ BESTANDEN - -**Datum:** 2026-07-31 -**Git-Commit:** 94847ea -**Test-Service:** g13zwdav6myvpnop96dj7tpx (crmtest.media-on.de) -**Image:** dx4pqdziu4uj6x9fxs1u5z0x:94847ea - -### Durchgeführte Änderungen - -1. **Plugin-Registry-Initialisierung über Migrations-Engine:** - - `registry.initialize(get_migration_engine())` statt `get_worker_engine()` - - DDL-Operationen laufen als `crm_migration` (BYPASSRLS), nicht als `crm_worker` - -2. **Worker-Session über `get_worker_session_factory()`:** - - Worker verwendet `crm_worker` für alle DB-Operationen - - Keine Verwendung von `get_session_factory()` (crm_api) im Worker - -3. **Event-Handler nur für aktive Plugins:** - - `PluginModel.active == True` Check vor `register_event_handlers()` - - Inaktive Plugins werden übersprungen - -4. **Per-Tenant Outbox-Processing:** - - `process_outbox_batch` iteriert über alle Tenant-IDs - - Setzt `app.current_tenant_id` vor jedem Claim - - RLS-kompatibel — kein BYPASSRLS für Outbox-Processing - - `process_outbox_job` lädt Tenant-IDs und übergibt sie an `process_outbox_batch` - -5. **Outbox-Event-Verarbeitung:** - - Events ohne Handler → Status `no_handlers` (nicht `published`) - - Idempotency-Check über `consumer_inbox` - - Retry mit exponentiellem Backoff bei Fehlern - -### Verifikationsergebnisse - -| Kriterium | Ergebnis | -|-----------|----------| -| Worker healthy | ✅ Up 2 minutes (healthy) | -| API healthy | ✅ Up 2 minutes (healthy) | -| Worker verarbeitet Outbox-Jobs | ✅ Alle 5 Sekunden, 0.01s pro Job | -| Worker verarbeitet scheduler_tick | ✅ Alle 5 Minuten | -| Worker übernimmt enqueued Jobs | ✅ send_password_reset_email übernommen | -| Worker verwendet crm_worker | ✅ get_worker_session_factory() | -| Plugin-Eventhandler für aktive Plugins | ✅ PluginModel.active Check | -| Keine Plugin-Router im Worker | ✅ Nur Event-Handler registriert | -| Outbox per-Tenant mit RLS-Kontext | ✅ set_config(app.current_tenant_id) | -| 18 Worker-Funktionen registriert | ✅ send_password_reset_email, generate_report_job, index_mails, etc. | - -### Ausgeführte Befehle - -``` -# Image bauen -git clone https://forgejo.media-on.de/Leopoldadmin/leocrm.git -git checkout 94847ea -docker build -t dx4pqdziu4uj6x9fxs1u5z0x:94847ea . - -# Deploy -docker compose up -d - -# Worker-Logs prüfen -docker logs worker-g13zwdav6myvpnop96dj7tpx - -# Job enqueue testen -docker exec worker-g13zwdav6myvpnop96dj7tpx python3 -c " -import asyncio -from arq import create_pool -from arq.connections import RedisSettings - -async def enqueue(): - settings = RedisSettings.from_dsn('redis://default:TestRedisPass2026@redis:6379/0') - redis = await create_pool(settings) - await redis.enqueue_job('send_password_reset_email', email='admin@media-on.de') - print('Job enqueued successfully') - await redis.close() - -asyncio.run(enqueue()) -" -``` - -### Bekannte Issues - -1. **Python-Logger-Ausgaben nicht in Docker-Logs sichtbar:** ARQ's Console-Handler zeigt nur Cron-Job-Output, nicht die `logger.info` Aufrufe aus `on_startup`. Die Logs werden möglicherweise in eine andere Log-Sink geschrieben. Kein Funktionsproblem. -2. **send_password_reset_email erwartet kein tenant_id Keyword:** Der Test-Job wurde mit `tenant_id` enqueued was die Funktion nicht erwartet. Das ist ein Test-Fehler, kein Worker-Fehler. Die Funktion übernimmt den Job korrekt. - -### Gate-5-Abnahme: BESTANDEN - -Der Worker ist healthy, verarbeitet Outbox-Jobs, übernimmt enqueued Jobs, und verwendet die korrekte Datenbankrolle (crm_worker). Plugin-Eventhandler werden nur für aktive Plugins registriert. Outbox-Processing läuft per-Tenant mit gesetztem RLS-Kontext. - ---- - -## Gate 3 — Vollständiger Restore-Test ✅ BESTANDEN - -**Datum:** 2026-07-31 -**Git-Commit:** 9b4ee3b -**Backup:** Forgejo Release `phase1-backup` (crm_backup_phase1.dump, 7.8 MB) -**Restore-DB:** crm_restore_test (separate Datenbank im Test-DB-Container) - -### Durchführung - -1. Backup aus Forgejo-Release heruntergeladen -2. MD5-Prüfsumme verglichen: b8003deaea95fb26f718ecb8a1a1369a ✅ -3. Separate leere Datenbank `crm_restore_test` erstellt -4. `pg_restore --no-owner --no-acl` in crm_restore_test ausgeführt -5. `alembic current` → 0086 (Backup-Stand) -6. `alembic upgrade head` → 0090 (Migrationen 0087-0090 angewendet) -7. Grants und Rollen-Passwörter neu angewendet (pg_restore --no-acl überspringt Grants) -8. RLS-Tests auf wiederhergestellter DB ausgeführt - -### Verifikationsergebnisse - -| Kriterium | Ergebnis | -|-----------|----------| -| Backup-Prüfsumme | ✅ MD5: b8003deaea95fb26f718ecb8a1a1369a | -| Restore erfolgreich | ✅ 123 Tabellen, 2 Tenants, 9 Contacts, 1 User, 479 Sessions | -| Alembic-Version nach Restore | ✅ 0086 (Backup-Stand) | -| Alembic upgrade head | ✅ 0090 (0087-0090 angewendet) | -| Datenintegrität erhalten | ✅ 9 Contacts (1 Tenant A, 8 Tenant B) | -| RLS ohne Kontext | ✅ 0 rows (fail-closed) | -| RLS mit Tenant B | ✅ 8 rows | -| RLS mit Tenant A | ✅ 2 rows | -| Cross-Tenant INSERT blockiert | ✅ 'new row violates row-level security policy' | -| DDL durch crm_api blockiert | ✅ 'permission denied for schema public' | -| RLS-Tabellen | ✅ 108 | -| RLS-Policies | ✅ 112 | -| Legacy Policies | ✅ 0 | - -### Bekannte Issues - -1. **pg_restore --no-acl überspringt Grants:** Nach dem Restore müssen GRANT-Statements neu angewendet werden. Dies ist ein bekanntes Verhalten von `pg_restore --no-acl`. In einer produktiven Restore-Prozedur sollten die Grants durch `alembic upgrade head` (Migration 0085) oder ein separates Grant-Skript neu angewendet werden. -2. **DMS-Dateien nicht getestet:** Der Restore-Test umfasste nur die PostgreSQL-Datenbank. DMS/Object-Storage-Dateien wurden nicht separat wiederhergestellt. Der Storage-Volume ist im Test-Service vorhanden aber nicht Teil des DB-Backups. - -### Gate-3-Abnahme: BESTANDEN - -Der Restore-Test ist erfolgreich abgeschlossen. Die Datenbank wurde aus dem Forgejo-Backup wiederhergestellt, auf den aktuellen Alembic-Head migriert, und alle RLS-Tests bestanden. diff --git a/docs/quality-gate-phase1.md b/docs/quality-gate-phase1.md deleted file mode 100644 index f874bf8..0000000 --- a/docs/quality-gate-phase1.md +++ /dev/null @@ -1,172 +0,0 @@ -# Quality Gate Review — Phase 1 → Phase 2 (Re-Review) - -**Datum:** 2026-06-28 -**Dateien:** requirements.md (2142 Zeilen), extracted-architecture-details.md (1006 Zeilen) -**Vorherige Findings:** 6 Fixes angewendet - ---- - -## Prüfkriterien & Ergebnisse - -### 1. Vollständigkeit: 143 Features (73 Core + 70 Plugin) -**Status: ✅ PASS** - -Verifikation: -- Active feature headings (exkl. historisch): 143 -- `[v1]`-Features (Core): 73 -- `[v2-Plugin]`-Features (Plugin): 70 -- `[v1-Plugin]`-Features: 0 (alle konvertiert) -- Summary-Tabelle: 73 Core + 70 Plugin = 143 -- DISCOVERY_CHECK_FINAL: `features_with_ids=143/143` - -### 2. Konsistenz: Plugin vs Core Trennung -**Status: ✅ PASS** - -Verifikation: -- F-FILE-01–04: alle `[v2-Plugin]` (vorher `[v1-Plugin]`) -- F-DMS-01–07: alle `[v2-Plugin]` -- F-LINK-01–06: alle `[v2-Plugin]` -- F-TAG-01–04: alle `[v2-Plugin]` -- F-PERM-01–06: alle `[v2-Plugin]` -- F-FILEUI-01–06: alle `[v2-Plugin]` -- F-CAL-01–18: alle `[v2-Plugin]` (F-CAL-10 = `[v2-Plugin — später]`) -- F-MAIL-01–19: alle `[v2-Plugin]` -- F-PLUGIN-01/02: `[v1]` (Plugin-System ist Core) -- F-CORE-04 (UI-Plugin-Framework): `[v1]` (Core-Infrastruktur) -- Summary-Header: `### Core-Features (v1)` und `### Plugin-Features (v2-Plugin)` - -### 3. Keine Implementierungs-Details in requirements.md -**Status: ✅ PASS** - -Verifikation: -- Keine HTML-Tags (`
`, ``, `