diff --git a/backend-compliance/compliance/api/ai_routes.py b/backend-compliance/compliance/api/ai_routes.py index 6e2997f9..5e31818b 100644 --- a/backend-compliance/compliance/api/ai_routes.py +++ b/backend-compliance/compliance/api/ai_routes.py @@ -162,7 +162,7 @@ async def update_ai_system( db: Session = Depends(get_db), ): """Update an AI system.""" - from datetime import datetime + from datetime import datetime, timezone system = db.query(AISystemDB).filter(AISystemDB.id == system_id).first() if not system: @@ -226,7 +226,7 @@ async def assess_ai_system( db: Session = Depends(get_db), ): """Run AI Act risk assessment for an AI system.""" - from datetime import datetime + from datetime import datetime, timezone system = db.query(AISystemDB).filter(AISystemDB.id == system_id).first() if not system: diff --git a/backend-compliance/compliance/api/canonical_control_routes.py b/backend-compliance/compliance/api/canonical_control_routes.py index a8f80cdd..cac566fb 100644 --- a/backend-compliance/compliance/api/canonical_control_routes.py +++ b/backend-compliance/compliance/api/canonical_control_routes.py @@ -47,6 +47,8 @@ from compliance.services.canonical_control_service import ( _control_row, # re-exported for legacy test imports ) +logger = logging.getLogger(__name__) + router = APIRouter(prefix="/v1/canonical", tags=["canonical-controls"]) diff --git a/backend-compliance/compliance/api/dashboard_routes.py b/backend-compliance/compliance/api/dashboard_routes.py index a4d2fd79..88034085 100644 --- a/backend-compliance/compliance/api/dashboard_routes.py +++ b/backend-compliance/compliance/api/dashboard_routes.py @@ -14,7 +14,7 @@ Endpoints: """ import logging -from datetime import datetime, date, timedelta +from datetime import datetime, date, timedelta, timezone from calendar import month_abbr from typing import Optional, Dict, Any, List from decimal import Decimal diff --git a/backend-compliance/compliance/api/dsfa_routes.py b/backend-compliance/compliance/api/dsfa_routes.py index 024737fe..1bc0fe0c 100644 --- a/backend-compliance/compliance/api/dsfa_routes.py +++ b/backend-compliance/compliance/api/dsfa_routes.py @@ -26,10 +26,11 @@ versions). Module-level helpers re-exported for legacy tests. import logging from typing import Any, List, Optional -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from fastapi.responses import Response from sqlalchemy.orm import Session +from sqlalchemy import text from classroom_engine.database import get_db from compliance.api._http_errors import translate_domain_errors @@ -484,6 +485,7 @@ async def list_dsfas( async def create_dsfa( request: DSFACreate, tenant_id: Optional[str] = Query(None), + db: Session = Depends(get_db), service: DSFAService = Depends(get_dsfa_service), ) -> dict[str, Any]: """Neue DSFA erstellen.""" diff --git a/backend-compliance/compliance/api/evidence_routes.py b/backend-compliance/compliance/api/evidence_routes.py index 29acba1b..1b7f201e 100644 --- a/backend-compliance/compliance/api/evidence_routes.py +++ b/backend-compliance/compliance/api/evidence_routes.py @@ -16,6 +16,11 @@ from the legacy path. """ import logging +import os +import json +import hashlib +import uuid as uuid_module +from datetime import datetime, timedelta from typing import Any, Optional from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile @@ -30,14 +35,15 @@ from ..db import ( EvidenceConfidenceEnum, EvidenceTruthStatusEnum, ) -from ..db.models import EvidenceDB, ControlDB, AuditTrailDB +from ..db.models import EvidenceDB, AuditTrailDB from ..services.auto_risk_updater import AutoRiskUpdater -from ..services.evidence_service import EvidenceService +from ..services.evidence_service import EvidenceService, _update_risks as _update_risks_impl from .schemas import ( EvidenceCreate, EvidenceResponse, EvidenceListResponse, EvidenceRejectRequest, ) from .audit_trail_utils import log_audit_trail +from ._http_errors import translate_domain_errors logger = logging.getLogger(__name__) router = APIRouter(tags=["compliance-evidence"]) @@ -146,6 +152,7 @@ async def list_evidence( status: Optional[str] = None, page: Optional[int] = Query(None, ge=1, description="Page number (1-based)"), limit: Optional[int] = Query(None, ge=1, le=500, description="Items per page"), + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> EvidenceListResponse: """List evidence with optional filters and pagination.""" @@ -186,9 +193,11 @@ async def list_evidence( @router.post("/evidence", response_model=EvidenceResponse) async def create_evidence( evidence_data: EvidenceCreate, + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> EvidenceResponse: """Create new evidence record.""" + dsms_cid = None repo = EvidenceRepository(db) # Get control UUID @@ -257,6 +266,7 @@ async def create_evidence( @router.delete("/evidence/{evidence_id}") async def delete_evidence( evidence_id: str, + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> dict[str, Any]: """Delete an evidence record.""" @@ -275,6 +285,7 @@ async def upload_evidence( title: str = Query(...), file: UploadFile = File(...), description: Optional[str] = Query(None), + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> EvidenceResponse: """Upload evidence file.""" @@ -674,6 +685,7 @@ async def collect_ci_evidence( async def get_ci_evidence_status( control_id: Optional[str] = Query(None, description="Filter by control ID"), days: int = Query(30, description="Look back N days"), + db: Session = Depends(get_db), service: EvidenceService = Depends(get_evidence_service), ) -> dict[str, Any]: """Get CI/CD evidence collection status overview.""" @@ -681,70 +693,8 @@ async def get_ci_evidence_status( return service.ci_status(control_id, days) -# ---------------------------------------------------------------------------- -# Legacy re-exports for tests that import helpers directly. -# ---------------------------------------------------------------------------- - - if control_id: - ctrl_repo = ControlRepository(db) - control = ctrl_repo.get_by_control_id(control_id) - if control: - query = query.filter(EvidenceDB.control_id == control.id) - - evidence_list = query.order_by(EvidenceDB.collected_at.desc()).limit(100).all() - - # Group by control and calculate stats - control_stats = defaultdict(lambda: { - "total": 0, - "valid": 0, - "failed": 0, - "last_collected": None, - "evidence": [], - }) - - for e in evidence_list: - # Get control_id string - control = db.query(ControlDB).filter(ControlDB.id == e.control_id).first() - ctrl_id = control.control_id if control else "unknown" - - stats = control_stats[ctrl_id] - stats["total"] += 1 - if e.status: - if e.status.value == "valid": - stats["valid"] += 1 - elif e.status.value == "failed": - stats["failed"] += 1 - if not stats["last_collected"] or e.collected_at > stats["last_collected"]: - stats["last_collected"] = e.collected_at - - # Add evidence summary - stats["evidence"].append({ - "id": e.id, - "type": e.evidence_type, - "status": e.status.value if e.status else None, - "collected_at": e.collected_at.isoformat() if e.collected_at else None, - "ci_job_id": e.ci_job_id, - }) - - # Convert to list and sort - result = [] - for ctrl_id, stats in control_stats.items(): - result.append({ - "control_id": ctrl_id, - "total_evidence": stats["total"], - "valid_count": stats["valid"], - "failed_count": stats["failed"], - "last_collected": stats["last_collected"].isoformat() if stats["last_collected"] else None, - "recent_evidence": stats["evidence"][:5], - }) - - result.sort(key=lambda x: x["last_collected"] or "", reverse=True) - - return { - "period_days": days, - "total_evidence": len(evidence_list), - "controls": result, - } +# (Alte CI-Status-Implementierung entfernt — unerreichbarer Code nach `return +# service.ci_status(...)`; durch den Service ersetzt, `query` war nie initialisiert.) # ============================================================================ @@ -772,6 +722,7 @@ async def review_evidence( approval_status='first_approved'. A second (different) reviewer then sets second_reviewer and approval_status='approved'. """ + dsms_cid = None evidence = db.query(EvidenceDB).filter(EvidenceDB.id == evidence_id).first() if not evidence: raise HTTPException(status_code=404, detail=f"Evidence {evidence_id} not found") @@ -851,6 +802,7 @@ async def reject_evidence( db: Session = Depends(get_db), ): """Reject evidence (sets approval_status='rejected').""" + dsms_cid = None evidence = db.query(EvidenceDB).filter(EvidenceDB.id == evidence_id).first() if not evidence: raise HTTPException(status_code=404, detail=f"Evidence {evidence_id} not found") diff --git a/backend-compliance/compliance/api/routes.py b/backend-compliance/compliance/api/routes.py index a3693475..c0a20760 100644 --- a/backend-compliance/compliance/api/routes.py +++ b/backend-compliance/compliance/api/routes.py @@ -24,6 +24,7 @@ from fastapi.responses import FileResponse from sqlalchemy.orm import Session from classroom_engine.database import get_db +from ..db.models import EvidenceDB from .audit_trail_utils import log_audit_trail from ..db import ( @@ -310,6 +311,7 @@ async def list_controls_paginated( ) async def get_control( control_id: str, + db: Session = Depends(get_db), svc: ControlExportService = Depends(get_ctrl_export_service), ) -> ControlResponse: """Get a specific control by control_id.""" @@ -354,6 +356,7 @@ async def get_control( async def update_control( control_id: str, update: ControlUpdate, + db: Session = Depends(get_db), svc: ControlExportService = Depends(get_ctrl_export_service), ) -> ControlResponse: """Update a control.""" @@ -443,6 +446,7 @@ async def update_control( async def review_control( control_id: str, review: ControlReviewRequest, + db: Session = Depends(get_db), svc: ControlExportService = Depends(get_ctrl_export_service), ) -> ControlResponse: """Mark a control as reviewed with new status.""" diff --git a/backend-compliance/compliance/api/vvt_routes.py b/backend-compliance/compliance/api/vvt_routes.py index 66e44f6e..0cbb8d83 100644 --- a/backend-compliance/compliance/api/vvt_routes.py +++ b/backend-compliance/compliance/api/vvt_routes.py @@ -21,7 +21,7 @@ Phase 1 Step 4 refactor: handlers delegate to VVTService. import logging from typing import Any, List, Optional -from fastapi import APIRouter, Depends, Query, Request +from fastapi import APIRouter, Depends, HTTPException, Query, Request from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session