"""Phase 11: Knowledge Management router — Playbooks + Lessons Learned.""" from typing import List, Optional from uuid import UUID from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from app.database import get_db from app.dependencies.auth import get_current_user, require_any_role from app.schemas.knowledge_schema import ( PlaybookCreate, PlaybookUpdate, PlaybookOut, PlaybookVersionOut, LessonLearnedCreate, LessonLearnedUpdate, LessonLearnedOut, ) from app.services import playbook_service as pb_svc from app.services import lesson_learned_service as ll_svc router = APIRouter(prefix="/knowledge", tags=["knowledge"]) # ══════════════════════════════════════════════════════════════════════════════ # Playbooks # ══════════════════════════════════════════════════════════════════════════════ @router.get("/playbooks", response_model=List[PlaybookOut]) def list_playbooks( technique_id: Optional[UUID] = None, playbook_type: Optional[str] = None, include_inactive: bool = False, db: Session = Depends(get_db), user=Depends(get_current_user), ): return pb_svc.list_playbooks( db, technique_id=technique_id, playbook_type=playbook_type, include_inactive=include_inactive, ) @router.post("/playbooks", response_model=PlaybookOut, status_code=201) def create_playbook( body: PlaybookCreate, db: Session = Depends(get_db), user=Depends(require_any_role("admin", "red_lead", "blue_lead")), ): return pb_svc.create_playbook(db, body.model_dump(), user.id) @router.get("/playbooks/{playbook_id}", response_model=PlaybookOut) def get_playbook( playbook_id: UUID, db: Session = Depends(get_db), user=Depends(get_current_user), ): return pb_svc.get_playbook(db, playbook_id) @router.patch("/playbooks/{playbook_id}", response_model=PlaybookOut) def update_playbook( playbook_id: UUID, body: PlaybookUpdate, db: Session = Depends(get_db), user=Depends(require_any_role("admin", "red_lead", "blue_lead")), ): return pb_svc.update_playbook(db, playbook_id, body.model_dump(exclude_unset=True), user.id) @router.delete("/playbooks/{playbook_id}", status_code=204) def delete_playbook( playbook_id: UUID, db: Session = Depends(get_db), user=Depends(require_any_role("admin", "red_lead", "blue_lead")), ): pb_svc.delete_playbook(db, playbook_id, user.id) # ── Versions ────────────────────────────────────────────────────────────────── @router.get("/playbooks/{playbook_id}/versions", response_model=List[PlaybookVersionOut]) def list_versions( playbook_id: UUID, db: Session = Depends(get_db), user=Depends(get_current_user), ): return pb_svc.get_playbook_versions(db, playbook_id) @router.post("/playbooks/{playbook_id}/restore/{version}", response_model=PlaybookOut) def restore_version( playbook_id: UUID, version: int, db: Session = Depends(get_db), user=Depends(require_any_role("admin", "red_lead", "blue_lead")), ): """Roll the playbook back to a specific historical version.""" return pb_svc.restore_version(db, playbook_id, version, user.id) # ── By technique (convenience) ──────────────────────────────────────────────── @router.get( "/techniques/{technique_id}/playbooks", response_model=List[PlaybookOut], ) def playbooks_for_technique( technique_id: UUID, db: Session = Depends(get_db), user=Depends(get_current_user), ): """List all active playbooks for a specific technique.""" return pb_svc.list_playbooks(db, technique_id=technique_id) @router.get( "/techniques/{technique_id}/playbooks/{playbook_type}", response_model=PlaybookOut, ) def get_playbook_by_technique_type( technique_id: UUID, playbook_type: str, db: Session = Depends(get_db), user=Depends(get_current_user), ): pb = pb_svc.get_playbook_by_technique_type(db, technique_id, playbook_type) if not pb: from app.domain.errors import EntityNotFoundError raise EntityNotFoundError("Playbook", f"{technique_id}/{playbook_type}") return pb # ══════════════════════════════════════════════════════════════════════════════ # Lessons Learned # ══════════════════════════════════════════════════════════════════════════════ @router.get("/lessons", response_model=List[LessonLearnedOut]) def list_lessons( entity_type: Optional[str] = None, entity_id: Optional[UUID] = None, severity: Optional[str] = None, tag: Optional[str] = None, technique_id: Optional[str] = None, include_inactive: bool = False, db: Session = Depends(get_db), user=Depends(get_current_user), ): return ll_svc.list_lessons_learned( db, entity_type=entity_type, entity_id=entity_id, severity=severity, tag=tag, technique_id=technique_id, include_inactive=include_inactive, ) @router.post("/lessons", response_model=LessonLearnedOut, status_code=201) def create_lesson( body: LessonLearnedCreate, db: Session = Depends(get_db), user=Depends(require_any_role("admin", "red_lead", "blue_lead")), ): return ll_svc.create_lesson_learned(db, body.model_dump(), user.id) @router.get("/lessons/{lesson_id}", response_model=LessonLearnedOut) def get_lesson( lesson_id: UUID, db: Session = Depends(get_db), user=Depends(get_current_user), ): return ll_svc.get_lesson_learned(db, lesson_id) @router.patch("/lessons/{lesson_id}", response_model=LessonLearnedOut) def update_lesson( lesson_id: UUID, body: LessonLearnedUpdate, db: Session = Depends(get_db), user=Depends(require_any_role("admin", "red_lead", "blue_lead")), ): return ll_svc.update_lesson_learned( db, lesson_id, body.model_dump(exclude_unset=True), user.id ) @router.delete("/lessons/{lesson_id}", status_code=204) def delete_lesson( lesson_id: UUID, db: Session = Depends(get_db), user=Depends(require_any_role("admin", "red_lead", "blue_lead")), ): """Soft-delete a lesson (admin / lead only).""" ll_svc.delete_lesson_learned(db, lesson_id, user.id) # ── Stats ───────────────────────────────────────────────────────────────────── @router.get("/stats") def knowledge_stats( db: Session = Depends(get_db), user=Depends(get_current_user), ): """Summary counts: total playbooks, lessons by severity, playbooks by type.""" return ll_svc.get_knowledge_stats(db)