"""Organisations-CRUD.""" from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, status, Request from models import OrgCreate, OrgUpdate, OrgResponse from auth import get_current_admin from database import db_dependency from audit import log_action, get_client_ip, row_to_dict import aiosqlite router = APIRouter(prefix="/api/orgs", tags=["organizations"]) async def _enrich_org(db: aiosqlite.Connection, row: aiosqlite.Row) -> dict: org = dict(row) cursor = await db.execute( "SELECT COUNT(*) as cnt FROM users WHERE organization_id = ? AND is_active = 1", (org["id"],), ) org["user_count"] = (await cursor.fetchone())["cnt"] cursor = await db.execute( "SELECT license_type, status FROM licenses WHERE organization_id = ? AND status = 'active' ORDER BY created_at DESC LIMIT 1", (org["id"],), ) lic = await cursor.fetchone() org["license_status"] = lic["status"] if lic else "none" org["license_type"] = lic["license_type"] if lic else "" # output_language aus organization_settings (Default 'de') cursor = await db.execute( "SELECT value FROM organization_settings WHERE organization_id = ? AND key = 'output_language'", (org["id"],), ) lang_row = await cursor.fetchone() org["output_language"] = lang_row["value"] if lang_row else "de" # ai_backend aus organization_settings (leer = Servervorgabe) cursor = await db.execute( "SELECT value FROM organization_settings WHERE organization_id = ? AND key = 'ai_backend'", (org["id"],), ) ai_row = await cursor.fetchone() org["ai_backend"] = (ai_row["value"] if ai_row else "") or "" return org @router.get("", response_model=list[OrgResponse]) async def list_organizations( admin: dict = Depends(get_current_admin), db: aiosqlite.Connection = Depends(db_dependency), ): cursor = await db.execute("SELECT * FROM organizations ORDER BY created_at DESC") rows = await cursor.fetchall() return [await _enrich_org(db, row) for row in rows] @router.post("", response_model=OrgResponse, status_code=status.HTTP_201_CREATED) async def create_organization( data: OrgCreate, request: Request, admin: dict = Depends(get_current_admin), db: aiosqlite.Connection = Depends(db_dependency), ): cursor = await db.execute("SELECT id FROM organizations WHERE slug = ?", (data.slug,)) if await cursor.fetchone(): raise HTTPException(status_code=400, detail="Slug bereits vergeben") now = datetime.now(timezone.utc).isoformat() cursor = await db.execute( "INSERT INTO organizations (name, slug, is_active, created_at, updated_at) VALUES (?, ?, 1, ?, ?)", (data.name, data.slug, now, now), ) org_id = cursor.lastrowid # Optional direkt eine Lizenz mit Credits-Kontingent anlegen. Beide Inserts # liegen VOR dem Commit, schlaegt der Lizenz-Teil fehl (z.B. fehlende # Spalten), entsteht keine halb angelegte Organisation. lic_id = None revoked_lics: list = [] if data.license is not None: from routers.licenses import insert_license_row lic_id, revoked_lics = await insert_license_row(db, org_id, data.license) await db.commit() # output_language als organization_settings-Eintrag persistieren from shared.services.org_settings import set_org_setting await set_org_setting(db, org_id, "output_language", data.output_language) await set_org_setting(db, org_id, "ai_backend", data.ai_backend or "") cursor = await db.execute("SELECT * FROM organizations WHERE id = ?", (org_id,)) new_row_obj = await cursor.fetchone() await log_action( db, admin, get_client_ip(request), action="create", resource_type="organization", resource_id=org_id, after=dict(new_row_obj), ) if lic_id is not None: from routers.licenses import audit_license_creation await audit_license_creation( db, admin, get_client_ip(request), lic_id, revoked_lics ) return await _enrich_org(db, new_row_obj) @router.get("/{org_id}", response_model=OrgResponse) async def get_organization( org_id: int, admin: dict = Depends(get_current_admin), db: aiosqlite.Connection = Depends(db_dependency), ): cursor = await db.execute("SELECT * FROM organizations WHERE id = ?", (org_id,)) row = await cursor.fetchone() if not row: raise HTTPException(status_code=404, detail="Organisation nicht gefunden") return await _enrich_org(db, row) @router.put("/{org_id}", response_model=OrgResponse) async def update_organization( org_id: int, data: OrgUpdate, request: Request, admin: dict = Depends(get_current_admin), db: aiosqlite.Connection = Depends(db_dependency), ): before = await row_to_dict(db, "organizations", org_id) if not before: raise HTTPException(status_code=404, detail="Organisation nicht gefunden") updates = {} if data.name is not None: updates["name"] = data.name if data.is_active is not None: updates["is_active"] = 1 if data.is_active else 0 if updates: updates["updated_at"] = datetime.now(timezone.utc).isoformat() set_clause = ", ".join(f"{k} = ?" for k in updates) values = list(updates.values()) + [org_id] await db.execute(f"UPDATE organizations SET {set_clause} WHERE id = ?", values) await db.commit() # output_language separat ueber organization_settings setzen if data.output_language is not None: from shared.services.org_settings import set_org_setting await set_org_setting(db, org_id, "output_language", data.output_language) # KI-Weg. Der Leerstring ist ein gueltiger Wert und bedeutet # "Servervorgabe", deshalb hier die Pruefung auf None statt auf Wahrheit. if data.ai_backend is not None: from shared.services.org_settings import set_org_setting await set_org_setting(db, org_id, "ai_backend", data.ai_backend) after = await row_to_dict(db, "organizations", org_id) await log_action( db, admin, get_client_ip(request), action="update", resource_type="organization", resource_id=org_id, before=before, after=after, ) cursor = await db.execute("SELECT * FROM organizations WHERE id = ?", (org_id,)) return await _enrich_org(db, await cursor.fetchone()) @router.delete("/{org_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_organization( org_id: int, request: Request, admin: dict = Depends(get_current_admin), db: aiosqlite.Connection = Depends(db_dependency), ): before = await row_to_dict(db, "organizations", org_id) if not before: raise HTTPException(status_code=404, detail="Organisation nicht gefunden") await db.execute("DELETE FROM organizations WHERE id = ?", (org_id,)) await db.commit() await log_action( db, admin, get_client_ip(request), action="delete", resource_type="organization", resource_id=org_id, before=before, )