refactor(d3): ARCH-051 — 14 dict-body-Routes auf Pydantic-Schemas umgestellt (entity_permissions bulk ×2, guests invite, users menu-order, system_settings backup-config+dsar, knowledge ×3, self_improvement ×5); DSAR-Export F821-Bug behoben (datetime/timezone undefined → NameError beim GDPR-Export), Zeitstempel auf datetime.now(UTC); Validierung jetzt im Schema statt in Routen
Check Cross-Plugin Imports / check (push) Has been cancelled

This commit is contained in:
Agent Zero
2026-08-24 10:55:22 +02:00
parent ef90d57f0a
commit c32e4bb34e
7 changed files with 189 additions and 118 deletions
+35 -15
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import uuid
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import select as sa_select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -18,6 +19,21 @@ from app.services import system_settings_service
router = APIRouter(prefix="/api/v1/system-settings", tags=["system-settings"])
class BackupConfigRequest(BaseModel):
"""Update backup configuration (all fields optional)."""
backup_enabled: bool | None = None
backup_interval: str | None = Field(None, max_length=20)
backup_retention_days: int | None = Field(None, ge=1, le=365)
backup_destination: str | None = Field(None, max_length=20)
class DsarRequest(BaseModel):
"""Submit a Data Subject Access Request."""
type: str = Field("access", pattern="^(access|deletion|rectification)$")
@router.get("", response_model=SystemSettingsResponse)
async def get_system_settings(
db: AsyncSession = Depends(get_db),
@@ -86,7 +102,7 @@ async def get_backup_config(
@router.put("/backup-config")
async def update_backup_config(
body: dict,
body: BackupConfigRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("settings:write")),
):
@@ -105,14 +121,15 @@ async def update_backup_config(
# Merge backup fields into existing data
data = dict(existing)
if "backup_enabled" in body:
data["backup_enabled"] = bool(body["backup_enabled"])
if "backup_interval" in body:
data["backup_interval"] = str(body["backup_interval"])
if "backup_retention_days" in body:
data["backup_retention_days"] = int(body["backup_retention_days"])
if "backup_destination" in body:
data["backup_destination"] = str(body["backup_destination"])
updates = body.model_dump(exclude_unset=True)
for key in (
"backup_enabled",
"backup_interval",
"backup_retention_days",
"backup_destination",
):
if key in updates:
data[key] = updates[key]
return await system_settings_service.upsert_system_settings(db, tenant_id, user_id, data)
@@ -184,13 +201,16 @@ async def dsgvo_export(
- Calendar events
- Communication messages
"""
import json
import io
import json
from datetime import UTC, datetime
from fastapi.responses import StreamingResponse
from sqlalchemy import select as sa_select
from app.models.user import User
from app.models.contact import Contact
from app.models.audit import AuditLog
from app.models.contact import Contact
from app.models.user import User
tenant_id = uuid.UUID(current_user["tenant_id"])
try:
@@ -198,7 +218,7 @@ async def dsgvo_export(
except ValueError:
raise HTTPException(400, detail={"detail": "Invalid user_id", "code": "invalid_id"}) from None
export_data = {"user_id": str(uid), "exported_at": datetime.now(timezone.utc).isoformat(), "data": {}}
export_data = {"user_id": str(uid), "exported_at": datetime.now(UTC).isoformat(), "data": {}}
# User profile
user_result = await db.execute(sa_select(User).where(User.id == uid))
@@ -243,7 +263,7 @@ async def dsgvo_export(
@router.post("/dsar/{user_id}")
async def dsar_request(
user_id: str,
body: dict,
body: DsarRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("system:admin")),
):
@@ -254,7 +274,7 @@ async def dsar_request(
"""
from app.core.jobs import enqueue_job
tenant_id = uuid.UUID(current_user["tenant_id"])
request_type = body.get("type", "access")
request_type = body.type
try:
uid = uuid.UUID(user_id)
except ValueError: