diff --git a/backend-compliance/compliance/api/ai_routes.py b/backend-compliance/compliance/api/ai_routes.py index 875b0f8..6e2997f 100644 --- a/backend-compliance/compliance/api/ai_routes.py +++ b/backend-compliance/compliance/api/ai_routes.py @@ -186,7 +186,7 @@ async def update_ai_system( if hasattr(system, key): setattr(system, key, value) - system.updated_at = datetime.utcnow() + system.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(system) @@ -266,7 +266,7 @@ async def assess_ai_system( except ValueError: system.classification = AIClassificationEnum.UNCLASSIFIED - system.assessment_date = datetime.utcnow() + system.assessment_date = datetime.now(timezone.utc) system.assessment_result = assessment_result system.obligations = _derive_obligations(classification) system.risk_factors = assessment_result.get("risk_factors", []) diff --git a/backend-compliance/compliance/api/audit_routes.py b/backend-compliance/compliance/api/audit_routes.py index 179fa9e..6e15cf2 100644 --- a/backend-compliance/compliance/api/audit_routes.py +++ b/backend-compliance/compliance/api/audit_routes.py @@ -9,7 +9,7 @@ Endpoints: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, List from uuid import uuid4 import hashlib @@ -204,7 +204,7 @@ async def start_audit_session( ) session.status = AuditSessionStatusEnum.IN_PROGRESS - session.started_at = datetime.utcnow() + session.started_at = datetime.now(timezone.utc) db.commit() return {"success": True, "message": "Audit session started", "status": "in_progress"} @@ -229,7 +229,7 @@ async def complete_audit_session( ) session.status = AuditSessionStatusEnum.COMPLETED - session.completed_at = datetime.utcnow() + session.completed_at = datetime.now(timezone.utc) db.commit() return {"success": True, "message": "Audit session completed", "status": "completed"} @@ -482,7 +482,7 @@ async def sign_off_item( # Update existing sign-off signoff.result = result_enum signoff.notes = request.notes - signoff.updated_at = datetime.utcnow() + signoff.updated_at = datetime.now(timezone.utc) else: # Create new sign-off signoff = AuditSignOffDB( @@ -497,11 +497,11 @@ async def sign_off_item( # Create digital signature if requested signature = None if request.sign: - timestamp = datetime.utcnow().isoformat() + timestamp = datetime.now(timezone.utc).isoformat() data = f"{result_enum.value}|{requirement_id}|{session.auditor_name}|{timestamp}" signature = hashlib.sha256(data.encode()).hexdigest() signoff.signature_hash = signature - signoff.signed_at = datetime.utcnow() + signoff.signed_at = datetime.now(timezone.utc) signoff.signed_by = session.auditor_name # Update session statistics @@ -523,7 +523,7 @@ async def sign_off_item( # Auto-start session if this is the first sign-off if session.status == AuditSessionStatusEnum.DRAFT: session.status = AuditSessionStatusEnum.IN_PROGRESS - session.started_at = datetime.utcnow() + session.started_at = datetime.now(timezone.utc) db.commit() db.refresh(signoff) @@ -587,7 +587,7 @@ async def get_sign_off( @router.get("/sessions/{session_id}/report/pdf") async def generate_audit_pdf_report( session_id: str, - language: str = Query("de", regex="^(de|en)$"), + language: str = Query("de", pattern="^(de|en)$"), include_signatures: bool = Query(True), db: Session = Depends(get_db), ): diff --git a/backend-compliance/compliance/api/banner_routes.py b/backend-compliance/compliance/api/banner_routes.py index f57c89e..9acc2f4 100644 --- a/backend-compliance/compliance/api/banner_routes.py +++ b/backend-compliance/compliance/api/banner_routes.py @@ -6,7 +6,7 @@ Public SDK-Endpoints (fuer Einbettung) + Admin-Endpoints (Konfiguration & Stats) import uuid import hashlib -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Optional, List from fastapi import APIRouter, Depends, HTTPException, Query, Header @@ -206,8 +206,8 @@ async def record_consent( existing.ip_hash = ip_hash existing.user_agent = body.user_agent existing.consent_string = body.consent_string - existing.expires_at = datetime.utcnow() + timedelta(days=365) - existing.updated_at = datetime.utcnow() + existing.expires_at = datetime.now(timezone.utc) + timedelta(days=365) + existing.updated_at = datetime.now(timezone.utc) db.flush() _log_banner_audit( @@ -227,7 +227,7 @@ async def record_consent( ip_hash=ip_hash, user_agent=body.user_agent, consent_string=body.consent_string, - expires_at=datetime.utcnow() + timedelta(days=365), + expires_at=datetime.now(timezone.utc) + timedelta(days=365), ) db.add(consent) db.flush() @@ -476,7 +476,7 @@ async def update_site_config( if val is not None: setattr(config, field, val) - config.updated_at = datetime.utcnow() + config.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(config) return _site_config_to_dict(config) diff --git a/backend-compliance/compliance/api/consent_template_routes.py b/backend-compliance/compliance/api/consent_template_routes.py index 930da4c..712ee38 100644 --- a/backend-compliance/compliance/api/consent_template_routes.py +++ b/backend-compliance/compliance/api/consent_template_routes.py @@ -11,7 +11,7 @@ Endpoints: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Header @@ -173,7 +173,7 @@ async def update_consent_template( set_clauses = ", ".join(f"{k} = :{k}" for k in updates) updates["id"] = template_id updates["tenant_id"] = tenant_id - updates["now"] = datetime.utcnow() + updates["now"] = datetime.now(timezone.utc) row = db.execute( text(f""" diff --git a/backend-compliance/compliance/api/control_generator_routes.py b/backend-compliance/compliance/api/control_generator_routes.py index e76df1f..97231c7 100644 --- a/backend-compliance/compliance/api/control_generator_routes.py +++ b/backend-compliance/compliance/api/control_generator_routes.py @@ -186,7 +186,7 @@ async def list_jobs( @router.get("/generate/review-queue") async def get_review_queue( - release_state: str = Query("needs_review", regex="^(needs_review|too_close|duplicate)$"), + release_state: str = Query("needs_review", pattern="^(needs_review|too_close|duplicate)$"), limit: int = Query(50, ge=1, le=200), ): """Get controls that need manual review.""" diff --git a/backend-compliance/compliance/api/crud_factory.py b/backend-compliance/compliance/api/crud_factory.py index 3e5de2d..a08ad3f 100644 --- a/backend-compliance/compliance/api/crud_factory.py +++ b/backend-compliance/compliance/api/crud_factory.py @@ -20,7 +20,7 @@ Usage: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Any, Dict, List, Optional from fastapi import APIRouter, Depends, HTTPException, Query @@ -171,7 +171,7 @@ def create_crud_router( updates: Dict[str, Any] = { "id": item_id, "tenant_id": tenant_id, - "updated_at": datetime.utcnow(), + "updated_at": datetime.now(timezone.utc), } set_clauses = ["updated_at = :updated_at"] diff --git a/backend-compliance/compliance/api/dashboard_routes.py b/backend-compliance/compliance/api/dashboard_routes.py index 182876d..ffa4128 100644 --- a/backend-compliance/compliance/api/dashboard_routes.py +++ b/backend-compliance/compliance/api/dashboard_routes.py @@ -10,7 +10,7 @@ Endpoints: """ import logging -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from calendar import month_abbr from typing import Optional @@ -167,7 +167,7 @@ async def get_executive_dashboard(db: Session = Depends(get_db)): # Trend data — only show current score, no simulated history trend_data = [] if total > 0: - now = datetime.utcnow() + now = datetime.now(timezone.utc) trend_data.append(TrendDataPoint( date=now.strftime("%Y-%m-%d"), score=round(score, 1), @@ -204,7 +204,7 @@ async def get_executive_dashboard(db: Session = Depends(get_db)): # Get upcoming deadlines controls = ctrl_repo.get_all() upcoming_deadlines = [] - today = datetime.utcnow().date() + today = datetime.now(timezone.utc).date() for ctrl in controls: if ctrl.next_review_at: @@ -280,7 +280,7 @@ async def get_executive_dashboard(db: Session = Depends(get_db)): top_risks=top_risks, upcoming_deadlines=upcoming_deadlines, team_workload=team_workload, - last_updated=datetime.utcnow().isoformat(), + last_updated=datetime.now(timezone.utc).isoformat(), ) @@ -305,7 +305,7 @@ async def get_compliance_trend( # Trend data — only current score, no simulated history trend_data = [] if total > 0: - now = datetime.utcnow() + now = datetime.now(timezone.utc) trend_data.append({ "date": now.strftime("%Y-%m-%d"), "score": round(current_score, 1), @@ -318,7 +318,7 @@ async def get_compliance_trend( "current_score": round(current_score, 1), "trend": trend_data, "period_months": months, - "generated_at": datetime.utcnow().isoformat(), + "generated_at": datetime.now(timezone.utc).isoformat(), } diff --git a/backend-compliance/compliance/api/dsfa_routes.py b/backend-compliance/compliance/api/dsfa_routes.py index dcd9ce7..b9e3ca7 100644 --- a/backend-compliance/compliance/api/dsfa_routes.py +++ b/backend-compliance/compliance/api/dsfa_routes.py @@ -20,7 +20,7 @@ Endpoints: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, List from fastapi import APIRouter, Depends, HTTPException, Query @@ -691,7 +691,7 @@ async def update_dsfa_status( params: dict = { "id": dsfa_id, "tid": tid, "status": request.status, - "approved_at": datetime.utcnow() if request.status == "approved" else None, + "approved_at": datetime.now(timezone.utc) if request.status == "approved" else None, "approved_by": request.approved_by, } row = db.execute( @@ -906,7 +906,7 @@ async def export_dsfa_json( dsfa_data = _dsfa_to_response(row) return { - "exported_at": datetime.utcnow().isoformat(), + "exported_at": datetime.now(timezone.utc).isoformat(), "format": format, "dsfa": dsfa_data, } diff --git a/backend-compliance/compliance/api/dsr_routes.py b/backend-compliance/compliance/api/dsr_routes.py index 776de71..506c1e4 100644 --- a/backend-compliance/compliance/api/dsr_routes.py +++ b/backend-compliance/compliance/api/dsr_routes.py @@ -7,7 +7,7 @@ Native Python/FastAPI Implementierung, ersetzt Go consent-service Proxy. import io import csv import uuid -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Optional, List, Dict, Any from fastapi import APIRouter, Depends, HTTPException, Query, Header @@ -168,7 +168,7 @@ def _get_tenant(x_tenant_id: Optional[str] = Header(None, alias='X-Tenant-ID')) def _generate_request_number(db: Session, tenant_id: str) -> str: """Generate next request number: DSR-YYYY-NNNNNN""" - year = datetime.utcnow().year + year = datetime.now(timezone.utc).year try: result = db.execute(text("SELECT nextval('compliance_dsr_request_number_seq')")) seq = result.scalar() @@ -275,7 +275,7 @@ async def create_dsr( if body.priority and body.priority not in VALID_PRIORITIES: raise HTTPException(status_code=400, detail=f"Invalid priority. Must be one of: {VALID_PRIORITIES}") - now = datetime.utcnow() + now = datetime.now(timezone.utc) deadline_days = DEADLINE_DAYS.get(body.request_type, 30) request_number = _generate_request_number(db, tenant_id) @@ -348,7 +348,7 @@ async def list_dsrs( query = query.filter(DSRRequestDB.priority == priority) if overdue_only: query = query.filter( - DSRRequestDB.deadline_at < datetime.utcnow(), + DSRRequestDB.deadline_at < datetime.now(timezone.utc), DSRRequestDB.status.notin_(["completed", "rejected", "cancelled"]), ) if search: @@ -399,7 +399,7 @@ async def get_dsr_stats( by_type[t] = base.filter(DSRRequestDB.request_type == t).count() # Overdue - now = datetime.utcnow() + now = datetime.now(timezone.utc) overdue = base.filter( DSRRequestDB.deadline_at < now, DSRRequestDB.status.notin_(["completed", "rejected", "cancelled"]), @@ -459,7 +459,7 @@ async def export_dsrs( if format == "json": return { - "exported_at": datetime.utcnow().isoformat(), + "exported_at": datetime.now(timezone.utc).isoformat(), "total": len(dsrs), "requests": [_dsr_to_dict(d) for d in dsrs], } @@ -506,7 +506,7 @@ async def process_deadlines( db: Session = Depends(get_db), ): """Verarbeitet Fristen und markiert ueberfaellige DSRs.""" - now = datetime.utcnow() + now = datetime.now(timezone.utc) tid = uuid.UUID(tenant_id) overdue = db.query(DSRRequestDB).filter( @@ -714,7 +714,7 @@ async def publish_template_version( if not version: raise HTTPException(status_code=404, detail="Version not found") - now = datetime.utcnow() + now = datetime.now(timezone.utc) version.status = "published" version.published_at = now version.published_by = "admin" @@ -766,7 +766,7 @@ async def update_dsr( dsr.internal_notes = body.internal_notes if body.assigned_to is not None: dsr.assigned_to = body.assigned_to - dsr.assigned_at = datetime.utcnow() + dsr.assigned_at = datetime.now(timezone.utc) if body.request_text is not None: dsr.request_text = body.request_text if body.affected_systems is not None: @@ -778,7 +778,7 @@ async def update_dsr( if body.objection_details is not None: dsr.objection_details = body.objection_details - dsr.updated_at = datetime.utcnow() + dsr.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(dsr) return _dsr_to_dict(dsr) @@ -797,7 +797,7 @@ async def delete_dsr( _record_history(db, dsr, "cancelled", comment="DSR storniert") dsr.status = "cancelled" - dsr.updated_at = datetime.utcnow() + dsr.updated_at = datetime.now(timezone.utc) db.commit() return {"success": True, "message": "DSR cancelled"} @@ -820,7 +820,7 @@ async def change_status( dsr = _get_dsr_or_404(db, dsr_id, tenant_id) _record_history(db, dsr, body.status, comment=body.comment) dsr.status = body.status - dsr.updated_at = datetime.utcnow() + dsr.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(dsr) return _dsr_to_dict(dsr) @@ -835,7 +835,7 @@ async def verify_identity( ): """Verifiziert die Identitaet des Antragstellers.""" dsr = _get_dsr_or_404(db, dsr_id, tenant_id) - now = datetime.utcnow() + now = datetime.now(timezone.utc) dsr.identity_verified = True dsr.verification_method = body.method @@ -868,9 +868,9 @@ async def assign_dsr( """Weist eine DSR einem Bearbeiter zu.""" dsr = _get_dsr_or_404(db, dsr_id, tenant_id) dsr.assigned_to = body.assignee_id - dsr.assigned_at = datetime.utcnow() + dsr.assigned_at = datetime.now(timezone.utc) dsr.assigned_by = "admin" - dsr.updated_at = datetime.utcnow() + dsr.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(dsr) return _dsr_to_dict(dsr) @@ -888,7 +888,7 @@ async def extend_deadline( if dsr.status in ("completed", "rejected", "cancelled"): raise HTTPException(status_code=400, detail="Cannot extend deadline for closed DSR") - now = datetime.utcnow() + now = datetime.now(timezone.utc) current_deadline = dsr.extended_deadline_at or dsr.deadline_at new_deadline = current_deadline + timedelta(days=body.days or 60) @@ -916,7 +916,7 @@ async def complete_dsr( if dsr.status in ("completed", "cancelled"): raise HTTPException(status_code=400, detail="DSR already completed or cancelled") - now = datetime.utcnow() + now = datetime.now(timezone.utc) _record_history(db, dsr, "completed", comment=body.summary) dsr.status = "completed" dsr.completed_at = now @@ -941,7 +941,7 @@ async def reject_dsr( if dsr.status in ("completed", "rejected", "cancelled"): raise HTTPException(status_code=400, detail="DSR already closed") - now = datetime.utcnow() + now = datetime.now(timezone.utc) _record_history(db, dsr, "rejected", comment=f"{body.reason} ({body.legal_basis})") dsr.status = "rejected" dsr.rejection_reason = body.reason @@ -1024,7 +1024,7 @@ async def send_communication( ): """Sendet eine Kommunikation.""" dsr = _get_dsr_or_404(db, dsr_id, tenant_id) - now = datetime.utcnow() + now = datetime.now(timezone.utc) comm = DSRCommunicationDB( tenant_id=uuid.UUID(tenant_id), @@ -1158,7 +1158,7 @@ async def update_exception_check( check.applies = body.applies check.notes = body.notes check.checked_by = "admin" - check.checked_at = datetime.utcnow() + check.checked_at = datetime.now(timezone.utc) db.commit() db.refresh(check) diff --git a/backend-compliance/compliance/api/einwilligungen_routes.py b/backend-compliance/compliance/api/einwilligungen_routes.py index 2e25913..964e1af 100644 --- a/backend-compliance/compliance/api/einwilligungen_routes.py +++ b/backend-compliance/compliance/api/einwilligungen_routes.py @@ -15,7 +15,7 @@ Endpoints: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, List, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query, Header @@ -131,7 +131,7 @@ async def upsert_catalog( if record: record.selected_data_point_ids = request.selected_data_point_ids record.custom_data_points = request.custom_data_points - record.updated_at = datetime.utcnow() + record.updated_at = datetime.now(timezone.utc) else: record = EinwilligungenCatalogDB( tenant_id=tenant_id, @@ -184,7 +184,7 @@ async def upsert_company( if record: record.data = request.data - record.updated_at = datetime.utcnow() + record.updated_at = datetime.now(timezone.utc) else: record = EinwilligungenCompanyDB(tenant_id=tenant_id, data=request.data) db.add(record) @@ -233,7 +233,7 @@ async def upsert_cookies( if record: record.categories = request.categories record.config = request.config - record.updated_at = datetime.utcnow() + record.updated_at = datetime.now(timezone.utc) else: record = EinwilligungenCookiesDB( tenant_id=tenant_id, @@ -374,7 +374,7 @@ async def create_consent( user_id=request.user_id, data_point_id=request.data_point_id, granted=request.granted, - granted_at=datetime.utcnow(), + granted_at=datetime.now(timezone.utc), consent_version=request.consent_version, source=request.source, ip_address=request.ip_address, @@ -443,7 +443,7 @@ async def revoke_consent( if consent.revoked_at: raise HTTPException(status_code=400, detail="Consent is already revoked") - consent.revoked_at = datetime.utcnow() + consent.revoked_at = datetime.now(timezone.utc) _record_history(db, consent, 'revoked') db.commit() db.refresh(consent) diff --git a/backend-compliance/compliance/api/email_template_routes.py b/backend-compliance/compliance/api/email_template_routes.py index 2af10a5..0592784 100644 --- a/backend-compliance/compliance/api/email_template_routes.py +++ b/backend-compliance/compliance/api/email_template_routes.py @@ -6,7 +6,7 @@ Inklusive Versionierung, Approval-Workflow, Vorschau und Send-Logging. """ import uuid -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, Dict from fastapi import APIRouter, Depends, HTTPException, Query, Header @@ -271,7 +271,7 @@ async def update_settings( if val is not None: setattr(settings, field, val) - settings.updated_at = datetime.utcnow() + settings.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(settings) @@ -638,7 +638,7 @@ async def submit_version( raise HTTPException(status_code=400, detail="Only draft versions can be submitted") v.status = "review" - v.submitted_at = datetime.utcnow() + v.submitted_at = datetime.now(timezone.utc) v.submitted_by = "admin" db.commit() db.refresh(v) @@ -730,7 +730,7 @@ async def publish_version( if v.status not in ("approved", "review", "draft"): raise HTTPException(status_code=400, detail="Version cannot be published") - now = datetime.utcnow() + now = datetime.now(timezone.utc) v.status = "published" v.published_at = now v.published_by = "admin" diff --git a/backend-compliance/compliance/api/escalation_routes.py b/backend-compliance/compliance/api/escalation_routes.py index 3a7e03c..492acc3 100644 --- a/backend-compliance/compliance/api/escalation_routes.py +++ b/backend-compliance/compliance/api/escalation_routes.py @@ -12,7 +12,7 @@ Endpoints: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query, Header @@ -244,7 +244,7 @@ async def update_escalation( set_clauses = ", ".join(f"{k} = :{k}" for k in updates.keys()) updates["id"] = escalation_id - updates["updated_at"] = datetime.utcnow() + updates["updated_at"] = datetime.now(timezone.utc) row = db.execute( text( @@ -277,7 +277,7 @@ async def update_status( resolved_at = request.resolved_at if request.status in ('resolved', 'closed') and resolved_at is None: - resolved_at = datetime.utcnow() + resolved_at = datetime.now(timezone.utc) row = db.execute( text( @@ -288,7 +288,7 @@ async def update_status( { "status": request.status, "resolved_at": resolved_at, - "updated_at": datetime.utcnow(), + "updated_at": datetime.now(timezone.utc), "id": escalation_id, }, ).fetchone() diff --git a/backend-compliance/compliance/api/evidence_routes.py b/backend-compliance/compliance/api/evidence_routes.py index 4c202a1..b37f55d 100644 --- a/backend-compliance/compliance/api/evidence_routes.py +++ b/backend-compliance/compliance/api/evidence_routes.py @@ -10,7 +10,7 @@ Endpoints: import logging import os -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Optional from collections import defaultdict import uuid as uuid_module @@ -370,8 +370,8 @@ def _store_evidence( mime_type="application/json", source="ci_pipeline", ci_job_id=ci_job_id, - valid_from=datetime.utcnow(), - valid_until=datetime.utcnow() + timedelta(days=90), + valid_from=datetime.now(timezone.utc), + valid_until=datetime.now(timezone.utc) + timedelta(days=90), status=EvidenceStatusEnum(parsed["evidence_status"]), ) db.add(evidence) @@ -455,7 +455,7 @@ def _update_risks(db: Session, *, source: str, control_id: str, ci_job_id: str, tool=source, control_id=control_id, evidence_type=f"ci_{source}", - timestamp=datetime.utcnow().isoformat(), + timestamp=datetime.now(timezone.utc).isoformat(), commit_sha=report_data.get("commit_sha", "unknown") if report_data else "unknown", ci_job_id=ci_job_id, findings=findings_detail, @@ -571,7 +571,7 @@ async def get_ci_evidence_status( Returns overview of recent evidence collected from CI/CD pipelines, useful for dashboards and monitoring. """ - cutoff_date = datetime.utcnow() - timedelta(days=days) + cutoff_date = datetime.now(timezone.utc) - timedelta(days=days) # Build query query = db.query(EvidenceDB).filter( diff --git a/backend-compliance/compliance/api/extraction_routes.py b/backend-compliance/compliance/api/extraction_routes.py index fd2bcc3..111d6d9 100644 --- a/backend-compliance/compliance/api/extraction_routes.py +++ b/backend-compliance/compliance/api/extraction_routes.py @@ -18,7 +18,7 @@ import logging import re import asyncio from typing import Optional, List, Dict -from datetime import datetime +from datetime import datetime, timezone from fastapi import APIRouter, Depends from pydantic import BaseModel @@ -171,7 +171,7 @@ def _get_or_create_regulation( code=regulation_code, name=regulation_name or regulation_code, regulation_type=reg_type, - description=f"Auto-created from RAG extraction ({datetime.utcnow().date()})", + description=f"Auto-created from RAG extraction ({datetime.now(timezone.utc).date()})", ) return reg diff --git a/backend-compliance/compliance/api/isms_routes.py b/backend-compliance/compliance/api/isms_routes.py index 31c2b43..c43c0f1 100644 --- a/backend-compliance/compliance/api/isms_routes.py +++ b/backend-compliance/compliance/api/isms_routes.py @@ -13,7 +13,7 @@ Provides endpoints for ISO 27001 certification-ready ISMS management: import uuid import hashlib -from datetime import datetime, date +from datetime import datetime, date, timezone from typing import Optional from fastapi import APIRouter, HTTPException, Query, Depends @@ -102,7 +102,7 @@ def log_audit_trail( new_value=new_value, change_summary=change_summary, performed_by=performed_by, - performed_at=datetime.utcnow(), + performed_at=datetime.now(timezone.utc), checksum=create_signature(f"{entity_type}|{entity_id}|{action}|{performed_by}") ) db.add(trail) @@ -190,7 +190,7 @@ async def update_isms_scope( setattr(scope, field, value) scope.updated_by = updated_by - scope.updated_at = datetime.utcnow() + scope.updated_at = datetime.now(timezone.utc) # Increment version if significant changes version_parts = scope.version.split(".") @@ -221,11 +221,11 @@ async def approve_isms_scope( scope.status = ApprovalStatusEnum.APPROVED scope.approved_by = data.approved_by - scope.approved_at = datetime.utcnow() + scope.approved_at = datetime.now(timezone.utc) scope.effective_date = data.effective_date scope.review_date = data.review_date scope.approval_signature = create_signature( - f"{scope.scope_statement}|{data.approved_by}|{datetime.utcnow().isoformat()}" + f"{scope.scope_statement}|{data.approved_by}|{datetime.now(timezone.utc).isoformat()}" ) log_audit_trail(db, "isms_scope", scope.id, "ISMS Scope", "approve", data.approved_by) @@ -403,7 +403,7 @@ async def approve_policy( policy.reviewed_by = data.reviewed_by policy.approved_by = data.approved_by - policy.approved_at = datetime.utcnow() + policy.approved_at = datetime.now(timezone.utc) policy.effective_date = data.effective_date policy.next_review_date = date( data.effective_date.year + (policy.review_frequency_months // 12), @@ -412,7 +412,7 @@ async def approve_policy( ) policy.status = ApprovalStatusEnum.APPROVED policy.approval_signature = create_signature( - f"{policy.policy_id}|{data.approved_by}|{datetime.utcnow().isoformat()}" + f"{policy.policy_id}|{data.approved_by}|{datetime.now(timezone.utc).isoformat()}" ) log_audit_trail(db, "isms_policy", policy.id, policy.policy_id, "approve", data.approved_by) @@ -634,9 +634,9 @@ async def approve_soa_entry( raise HTTPException(status_code=404, detail="SoA entry not found") entry.reviewed_by = data.reviewed_by - entry.reviewed_at = datetime.utcnow() + entry.reviewed_at = datetime.now(timezone.utc) entry.approved_by = data.approved_by - entry.approved_at = datetime.utcnow() + entry.approved_at = datetime.now(timezone.utc) log_audit_trail(db, "soa", entry.id, entry.annex_a_control, "approve", data.approved_by) db.commit() @@ -812,7 +812,7 @@ async def close_finding( finding.verification_method = data.verification_method finding.verification_evidence = data.verification_evidence finding.verified_by = data.closed_by - finding.verified_at = datetime.utcnow() + finding.verified_at = datetime.now(timezone.utc) log_audit_trail(db, "audit_finding", finding.id, finding.finding_id, "close", data.closed_by) db.commit() @@ -1080,7 +1080,7 @@ async def approve_management_review( review.status = "approved" review.approved_by = data.approved_by - review.approved_at = datetime.utcnow() + review.approved_at = datetime.now(timezone.utc) review.next_review_date = data.next_review_date review.minutes_document_path = data.minutes_document_path @@ -1392,7 +1392,7 @@ async def run_readiness_check( # Save check result check = ISMSReadinessCheckDB( id=generate_id(), - check_date=datetime.utcnow(), + check_date=datetime.now(timezone.utc), triggered_by=data.triggered_by, overall_status=overall_status, certification_possible=certification_possible, diff --git a/backend-compliance/compliance/api/legal_document_routes.py b/backend-compliance/compliance/api/legal_document_routes.py index dd5286f..3750853 100644 --- a/backend-compliance/compliance/api/legal_document_routes.py +++ b/backend-compliance/compliance/api/legal_document_routes.py @@ -6,7 +6,7 @@ Extended with: Public endpoints, User Consents, Consent Audit Log, Cookie Catego import uuid as uuid_mod import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, List, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query, Header, UploadFile, File @@ -285,7 +285,7 @@ async def update_version( for field, value in request.dict(exclude_none=True).items(): setattr(version, field, value) - version.updated_at = datetime.utcnow() + version.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(version) @@ -346,7 +346,7 @@ def _transition( ) version.status = to_status - version.updated_at = datetime.utcnow() + version.updated_at = datetime.now(timezone.utc) if extra_updates: for k, v in extra_updates.items(): setattr(version, k, v) @@ -378,7 +378,7 @@ async def approve_version( return _transition( db, version_id, ['review'], 'approved', 'approved', request.approver, request.comment, - extra_updates={'approved_by': request.approver, 'approved_at': datetime.utcnow()} + extra_updates={'approved_by': request.approver, 'approved_at': datetime.now(timezone.utc)} ) @@ -728,7 +728,7 @@ async def withdraw_consent( if consent.withdrawn_at: raise HTTPException(status_code=400, detail="Consent already withdrawn") - consent.withdrawn_at = datetime.utcnow() + consent.withdrawn_at = datetime.now(timezone.utc) consent.consented = False _log_consent_audit( @@ -903,7 +903,7 @@ async def update_cookie_category( if val is not None: setattr(cat, field, val) - cat.updated_at = datetime.utcnow() + cat.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(cat) return _cookie_cat_to_dict(cat) diff --git a/backend-compliance/compliance/api/legal_template_routes.py b/backend-compliance/compliance/api/legal_template_routes.py index 9fb14b2..73f7c5e 100644 --- a/backend-compliance/compliance/api/legal_template_routes.py +++ b/backend-compliance/compliance/api/legal_template_routes.py @@ -15,7 +15,7 @@ Endpoints: import json import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, List, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query @@ -322,7 +322,7 @@ async def update_legal_template( params: Dict[str, Any] = { "id": template_id, "tenant_id": tenant_id, - "updated_at": datetime.utcnow(), + "updated_at": datetime.now(timezone.utc), } jsonb_fields = {"placeholders", "inspiration_sources"} diff --git a/backend-compliance/compliance/api/loeschfristen_routes.py b/backend-compliance/compliance/api/loeschfristen_routes.py index 3d01490..cfa6b72 100644 --- a/backend-compliance/compliance/api/loeschfristen_routes.py +++ b/backend-compliance/compliance/api/loeschfristen_routes.py @@ -13,7 +13,7 @@ Endpoints: import json import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, List, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query @@ -253,7 +253,7 @@ async def update_loeschfrist( ): """Full update of a Loeschfrist policy.""" - updates: Dict[str, Any] = {"id": policy_id, "tenant_id": tenant_id, "updated_at": datetime.utcnow()} + updates: Dict[str, Any] = {"id": policy_id, "tenant_id": tenant_id, "updated_at": datetime.now(timezone.utc)} set_clauses = ["updated_at = :updated_at"] for field, value in payload.model_dump(exclude_unset=True).items(): @@ -302,7 +302,7 @@ async def update_loeschfrist_status( WHERE id = :id AND tenant_id = :tenant_id RETURNING * """), - {"status": payload.status, "now": datetime.utcnow(), "id": policy_id, "tenant_id": tenant_id}, + {"status": payload.status, "now": datetime.now(timezone.utc), "id": policy_id, "tenant_id": tenant_id}, ).fetchone() db.commit() diff --git a/backend-compliance/compliance/api/notfallplan_routes.py b/backend-compliance/compliance/api/notfallplan_routes.py index 494815c..9699106 100644 --- a/backend-compliance/compliance/api/notfallplan_routes.py +++ b/backend-compliance/compliance/api/notfallplan_routes.py @@ -21,7 +21,7 @@ Endpoints: import json import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, List, Any from fastapi import APIRouter, Depends, HTTPException, Query, Header @@ -852,11 +852,11 @@ async def update_incident( # Auto-set timestamps based on status transitions if updates.get("status") == "reported" and not updates.get("reported_to_authority_at"): - updates["reported_to_authority_at"] = datetime.utcnow().isoformat() + updates["reported_to_authority_at"] = datetime.now(timezone.utc).isoformat() if updates.get("status") == "closed" and not updates.get("closed_at"): - updates["closed_at"] = datetime.utcnow().isoformat() + updates["closed_at"] = datetime.now(timezone.utc).isoformat() - updates["updated_at"] = datetime.utcnow().isoformat() + updates["updated_at"] = datetime.now(timezone.utc).isoformat() set_parts = [] for k in updates: @@ -984,7 +984,7 @@ async def update_template( if not updates: raise HTTPException(status_code=400, detail="No fields to update") - updates["updated_at"] = datetime.utcnow().isoformat() + updates["updated_at"] = datetime.now(timezone.utc).isoformat() set_clauses = ", ".join(f"{k} = :{k}" for k in updates) updates["id"] = template_id updates["tenant_id"] = tenant_id diff --git a/backend-compliance/compliance/api/obligation_routes.py b/backend-compliance/compliance/api/obligation_routes.py index 0aece4b..f25363d 100644 --- a/backend-compliance/compliance/api/obligation_routes.py +++ b/backend-compliance/compliance/api/obligation_routes.py @@ -12,7 +12,7 @@ Endpoints: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, List, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query, Header @@ -228,7 +228,7 @@ async def update_obligation( logger.info("update_obligation user_id=%s tenant_id=%s id=%s", x_user_id, tenant_id, obligation_id) import json - updates: Dict[str, Any] = {"id": obligation_id, "tenant_id": tenant_id, "updated_at": datetime.utcnow()} + updates: Dict[str, Any] = {"id": obligation_id, "tenant_id": tenant_id, "updated_at": datetime.now(timezone.utc)} set_clauses = ["updated_at = :updated_at"] for field, value in payload.model_dump(exclude_unset=True).items(): @@ -274,7 +274,7 @@ async def update_obligation_status( SET status = :status, updated_at = :now WHERE id = :id AND tenant_id = :tenant_id RETURNING * - """), {"status": payload.status, "now": datetime.utcnow(), "id": obligation_id, "tenant_id": tenant_id}).fetchone() + """), {"status": payload.status, "now": datetime.now(timezone.utc), "id": obligation_id, "tenant_id": tenant_id}).fetchone() db.commit() if not row: diff --git a/backend-compliance/compliance/api/quality_routes.py b/backend-compliance/compliance/api/quality_routes.py index 57b4c06..cf0e41b 100644 --- a/backend-compliance/compliance/api/quality_routes.py +++ b/backend-compliance/compliance/api/quality_routes.py @@ -10,7 +10,7 @@ Endpoints: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query @@ -177,7 +177,7 @@ async def create_metric( "threshold": payload.threshold, "trend": payload.trend, "ai_system": payload.ai_system, - "last_measured": payload.last_measured or datetime.utcnow(), + "last_measured": payload.last_measured or datetime.now(timezone.utc), }).fetchone() db.commit() return _row_to_dict(row) @@ -192,7 +192,7 @@ async def update_metric( ): """Update a quality metric.""" - updates: Dict[str, Any] = {"id": metric_id, "tenant_id": tenant_id, "updated_at": datetime.utcnow()} + updates: Dict[str, Any] = {"id": metric_id, "tenant_id": tenant_id, "updated_at": datetime.now(timezone.utc)} set_clauses = ["updated_at = :updated_at"] for field, value in payload.model_dump(exclude_unset=True).items(): @@ -296,7 +296,7 @@ async def create_test( "duration": payload.duration, "ai_system": payload.ai_system, "details": payload.details, - "last_run": payload.last_run or datetime.utcnow(), + "last_run": payload.last_run or datetime.now(timezone.utc), }).fetchone() db.commit() return _row_to_dict(row) @@ -311,7 +311,7 @@ async def update_test( ): """Update a quality test.""" - updates: Dict[str, Any] = {"id": test_id, "tenant_id": tenant_id, "updated_at": datetime.utcnow()} + updates: Dict[str, Any] = {"id": test_id, "tenant_id": tenant_id, "updated_at": datetime.now(timezone.utc)} set_clauses = ["updated_at = :updated_at"] for field, value in payload.model_dump(exclude_unset=True).items(): diff --git a/backend-compliance/compliance/api/routes.py b/backend-compliance/compliance/api/routes.py index 4edbec9..6c97915 100644 --- a/backend-compliance/compliance/api/routes.py +++ b/backend-compliance/compliance/api/routes.py @@ -16,7 +16,7 @@ import logging logger = logging.getLogger(__name__) import os -from datetime import datetime +from datetime import datetime, timezone from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks @@ -393,11 +393,11 @@ async def update_requirement(requirement_id: str, updates: dict, db: Session = D # Track audit changes if 'audit_status' in updates: - requirement.last_audit_date = datetime.utcnow() + requirement.last_audit_date = datetime.now(timezone.utc) # TODO: Get auditor from auth requirement.last_auditor = updates.get('auditor_name', 'api_user') - requirement.updated_at = datetime.utcnow() + requirement.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(requirement) diff --git a/backend-compliance/compliance/api/security_backlog_routes.py b/backend-compliance/compliance/api/security_backlog_routes.py index 11bf968..1473fd9 100644 --- a/backend-compliance/compliance/api/security_backlog_routes.py +++ b/backend-compliance/compliance/api/security_backlog_routes.py @@ -10,7 +10,7 @@ Endpoints: """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Optional, Any, Dict from fastapi import APIRouter, Depends, HTTPException, Query @@ -207,7 +207,7 @@ async def update_security_item( ): """Update a security backlog item.""" - updates: Dict[str, Any] = {"id": item_id, "tenant_id": tenant_id, "updated_at": datetime.utcnow()} + updates: Dict[str, Any] = {"id": item_id, "tenant_id": tenant_id, "updated_at": datetime.now(timezone.utc)} set_clauses = ["updated_at = :updated_at"] for field, value in payload.model_dump(exclude_unset=True).items(): diff --git a/backend-compliance/compliance/api/source_policy_router.py b/backend-compliance/compliance/api/source_policy_router.py index 7cdb2e9..57e0308 100644 --- a/backend-compliance/compliance/api/source_policy_router.py +++ b/backend-compliance/compliance/api/source_policy_router.py @@ -21,11 +21,11 @@ Endpoints: GET /api/v1/admin/compliance-report — Compliance report """ -from datetime import datetime +from datetime import datetime, timezone from typing import Optional from fastapi import APIRouter, HTTPException, Depends, Query -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field from sqlalchemy.orm import Session from database import get_db @@ -83,8 +83,7 @@ class SourceResponse(BaseModel): created_at: str updated_at: Optional[str] = None - class Config: - from_attributes = True + model_config = ConfigDict(from_attributes=True) class OperationUpdate(BaseModel): @@ -530,7 +529,7 @@ async def get_policy_stats(db: Session = Depends(get_db)): pii_rules = db.query(PIIRuleDB).filter(PIIRuleDB.active).count() # Count blocked content entries from today - today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + today_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) blocked_today = db.query(BlockedContentDB).filter( BlockedContentDB.created_at >= today_start, ).count() @@ -553,7 +552,7 @@ async def get_compliance_report(db: Session = Depends(get_db)): pii_rules = db.query(PIIRuleDB).filter(PIIRuleDB.active).all() return { - "report_date": datetime.utcnow().isoformat(), + "report_date": datetime.now(timezone.utc).isoformat(), "summary": { "active_sources": len(sources), "active_pii_rules": len(pii_rules), diff --git a/backend-compliance/compliance/api/vendor_compliance_routes.py b/backend-compliance/compliance/api/vendor_compliance_routes.py index 7a1dd40..7ed5e3f 100644 --- a/backend-compliance/compliance/api/vendor_compliance_routes.py +++ b/backend-compliance/compliance/api/vendor_compliance_routes.py @@ -49,7 +49,7 @@ vendor_findings, vendor_control_instances). import json import logging import uuid -from datetime import datetime +from datetime import datetime, timezone from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query @@ -69,7 +69,7 @@ DEFAULT_TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" # ============================================================================= def _now_iso() -> str: - return datetime.utcnow().isoformat() + "Z" + return datetime.now(timezone.utc).isoformat() + "Z" def _ok(data, status_code: int = 200): @@ -418,7 +418,7 @@ def create_vendor(body: dict = {}, db: Session = Depends(get_db)): data = _to_snake(body) vid = str(uuid.uuid4()) tid = data.get("tenant_id", DEFAULT_TENANT_ID) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() db.execute(text(""" INSERT INTO vendor_vendors ( @@ -498,7 +498,7 @@ def update_vendor(vendor_id: str, body: dict = {}, db: Session = Depends(get_db) raise HTTPException(404, "Vendor not found") data = _to_snake(body) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() # Build dynamic SET clause allowed = [ @@ -558,7 +558,7 @@ def patch_vendor_status(vendor_id: str, body: dict = {}, db: Session = Depends(g result = db.execute(text(""" UPDATE vendor_vendors SET status = :status, updated_at = :now WHERE id = :id - """), {"id": vendor_id, "status": new_status, "now": datetime.utcnow().isoformat()}) + """), {"id": vendor_id, "status": new_status, "now": datetime.now(timezone.utc).isoformat()}) db.commit() if result.rowcount == 0: raise HTTPException(404, "Vendor not found") @@ -620,7 +620,7 @@ def create_contract(body: dict = {}, db: Session = Depends(get_db)): data = _to_snake(body) cid = str(uuid.uuid4()) tid = data.get("tenant_id", DEFAULT_TENANT_ID) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() db.execute(text(""" INSERT INTO vendor_contracts ( @@ -682,7 +682,7 @@ def update_contract(contract_id: str, body: dict = {}, db: Session = Depends(get raise HTTPException(404, "Contract not found") data = _to_snake(body) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() allowed = [ "vendor_id", "file_name", "original_name", "mime_type", "file_size", @@ -781,7 +781,7 @@ def create_finding(body: dict = {}, db: Session = Depends(get_db)): data = _to_snake(body) fid = str(uuid.uuid4()) tid = data.get("tenant_id", DEFAULT_TENANT_ID) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() db.execute(text(""" INSERT INTO vendor_findings ( @@ -831,7 +831,7 @@ def update_finding(finding_id: str, body: dict = {}, db: Session = Depends(get_d raise HTTPException(404, "Finding not found") data = _to_snake(body) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() allowed = [ "vendor_id", "contract_id", "finding_type", "category", "severity", @@ -920,7 +920,7 @@ def create_control_instance(body: dict = {}, db: Session = Depends(get_db)): data = _to_snake(body) ciid = str(uuid.uuid4()) tid = data.get("tenant_id", DEFAULT_TENANT_ID) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() db.execute(text(""" INSERT INTO vendor_control_instances ( @@ -965,7 +965,7 @@ def update_control_instance(instance_id: str, body: dict = {}, db: Session = Dep raise HTTPException(404, "Control instance not found") data = _to_snake(body) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() allowed = [ "vendor_id", "control_id", "control_domain", @@ -1050,7 +1050,7 @@ def list_controls( def create_control(body: dict = {}, db: Session = Depends(get_db)): cid = str(uuid.uuid4()) tid = body.get("tenantId", body.get("tenant_id", DEFAULT_TENANT_ID)) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() db.execute(text(""" INSERT INTO vendor_compliance_controls ( diff --git a/backend-compliance/compliance/api/vvt_routes.py b/backend-compliance/compliance/api/vvt_routes.py index 2eb04ae..5fec2ad 100644 --- a/backend-compliance/compliance/api/vvt_routes.py +++ b/backend-compliance/compliance/api/vvt_routes.py @@ -119,7 +119,7 @@ async def upsert_organization( else: for field, value in request.dict(exclude_none=True).items(): setattr(org, field, value) - org.updated_at = datetime.utcnow() + org.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(org) @@ -291,7 +291,7 @@ async def update_activity( updates = request.dict(exclude_none=True) for field, value in updates.items(): setattr(act, field, value) - act.updated_at = datetime.utcnow() + act.updated_at = datetime.now(timezone.utc) _log_audit( db, @@ -408,7 +408,7 @@ async def export_activities( return _export_csv(activities) return { - "exported_at": datetime.utcnow().isoformat(), + "exported_at": datetime.now(timezone.utc).isoformat(), "organization": { "name": org.organization_name if org else "", "dpo_name": org.dpo_name if org else "", @@ -482,7 +482,7 @@ def _export_csv(activities: list) -> StreamingResponse: iter([output.getvalue()]), media_type='text/csv; charset=utf-8', headers={ - 'Content-Disposition': f'attachment; filename="vvt_export_{datetime.utcnow().strftime("%Y%m%d")}.csv"' + 'Content-Disposition': f'attachment; filename="vvt_export_{datetime.now(timezone.utc).strftime("%Y%m%d")}.csv"' }, ) diff --git a/backend-compliance/compliance/db/isms_repository.py b/backend-compliance/compliance/db/isms_repository.py index 188b090..e3ce768 100644 --- a/backend-compliance/compliance/db/isms_repository.py +++ b/backend-compliance/compliance/db/isms_repository.py @@ -10,7 +10,7 @@ Provides CRUD operations for ISO 27001 certification-related entities: """ import uuid -from datetime import datetime, date +from datetime import datetime, date, timezone from typing import List, Optional, Dict, Any, Tuple from sqlalchemy.orm import Session as DBSession @@ -94,11 +94,11 @@ class ISMSScopeRepository: import hashlib scope.status = ApprovalStatusEnum.APPROVED scope.approved_by = approved_by - scope.approved_at = datetime.utcnow() + scope.approved_at = datetime.now(timezone.utc) scope.effective_date = effective_date scope.review_date = review_date scope.approval_signature = hashlib.sha256( - f"{scope.scope_statement}|{approved_by}|{datetime.utcnow().isoformat()}".encode() + f"{scope.scope_statement}|{approved_by}|{datetime.now(timezone.utc).isoformat()}".encode() ).hexdigest() self.db.commit() @@ -185,7 +185,7 @@ class ISMSPolicyRepository: policy.status = ApprovalStatusEnum.APPROVED policy.reviewed_by = reviewed_by policy.approved_by = approved_by - policy.approved_at = datetime.utcnow() + policy.approved_at = datetime.now(timezone.utc) policy.effective_date = effective_date policy.next_review_date = date( effective_date.year + (policy.review_frequency_months // 12), @@ -193,7 +193,7 @@ class ISMSPolicyRepository: effective_date.day ) policy.approval_signature = hashlib.sha256( - f"{policy.policy_id}|{approved_by}|{datetime.utcnow().isoformat()}".encode() + f"{policy.policy_id}|{approved_by}|{datetime.now(timezone.utc).isoformat()}".encode() ).hexdigest() self.db.commit() @@ -472,7 +472,7 @@ class AuditFindingRepository: finding.verification_method = verification_method finding.verification_evidence = verification_evidence finding.verified_by = closed_by - finding.verified_at = datetime.utcnow() + finding.verified_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(finding) @@ -644,7 +644,7 @@ class ManagementReviewRepository: review.status = "approved" review.approved_by = approved_by - review.approved_at = datetime.utcnow() + review.approved_at = datetime.now(timezone.utc) review.next_review_date = next_review_date review.minutes_document_path = minutes_document_path @@ -761,7 +761,7 @@ class AuditTrailRepository: new_value=new_value, change_summary=change_summary, performed_by=performed_by, - performed_at=datetime.utcnow(), + performed_at=datetime.now(timezone.utc), checksum=hashlib.sha256( f"{entity_type}|{entity_id}|{action}|{performed_by}".encode() ).hexdigest(), diff --git a/backend-compliance/compliance/db/repository.py b/backend-compliance/compliance/db/repository.py index 6fc66d7..0122bb5 100644 --- a/backend-compliance/compliance/db/repository.py +++ b/backend-compliance/compliance/db/repository.py @@ -6,7 +6,7 @@ Provides CRUD operations and business logic queries for all compliance entities. from __future__ import annotations import uuid -from datetime import datetime, date +from datetime import datetime, date, timezone from typing import List, Optional, Dict, Any from sqlalchemy.orm import Session as DBSession, selectinload, joinedload @@ -86,7 +86,7 @@ class RegulationRepository: for key, value in kwargs.items(): if hasattr(regulation, key): setattr(regulation, key, value) - regulation.updated_at = datetime.utcnow() + regulation.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(regulation) return regulation @@ -425,7 +425,7 @@ class ControlRepository: control.status = status if status_notes: control.status_notes = status_notes - control.updated_at = datetime.utcnow() + control.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(control) return control @@ -435,10 +435,10 @@ class ControlRepository: control = self.get_by_control_id(control_id) if not control: return None - control.last_reviewed_at = datetime.utcnow() + control.last_reviewed_at = datetime.now(timezone.utc) from datetime import timedelta - control.next_review_at = datetime.utcnow() + timedelta(days=control.review_frequency_days) - control.updated_at = datetime.utcnow() + control.next_review_at = datetime.now(timezone.utc) + timedelta(days=control.review_frequency_days) + control.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(control) return control @@ -450,7 +450,7 @@ class ControlRepository: .filter( or_( ControlDB.next_review_at is None, - ControlDB.next_review_at <= datetime.utcnow() + ControlDB.next_review_at <= datetime.now(timezone.utc) ) ) .order_by(ControlDB.next_review_at) @@ -624,7 +624,7 @@ class EvidenceRepository: if not evidence: return None evidence.status = status - evidence.updated_at = datetime.utcnow() + evidence.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(evidence) return evidence @@ -749,7 +749,7 @@ class RiskRepository: risk.residual_likelihood, risk.residual_impact ) - risk.updated_at = datetime.utcnow() + risk.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(risk) return risk @@ -860,9 +860,9 @@ class AuditExportRepository: export.compliance_score = compliance_score if status == ExportStatusEnum.COMPLETED: - export.completed_at = datetime.utcnow() + export.completed_at = datetime.now(timezone.utc) - export.updated_at = datetime.utcnow() + export.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(export) return export @@ -1156,11 +1156,11 @@ class AuditSessionRepository: session.status = status if status == AuditSessionStatusEnum.IN_PROGRESS and not session.started_at: - session.started_at = datetime.utcnow() + session.started_at = datetime.now(timezone.utc) elif status == AuditSessionStatusEnum.COMPLETED: - session.completed_at = datetime.utcnow() + session.completed_at = datetime.now(timezone.utc) - session.updated_at = datetime.utcnow() + session.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(session) return session @@ -1183,7 +1183,7 @@ class AuditSessionRepository: if completed_items is not None: session.completed_items = completed_items - session.updated_at = datetime.utcnow() + session.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(session) return session @@ -1207,9 +1207,9 @@ class AuditSessionRepository: total_requirements = query.scalar() or 0 session.status = AuditSessionStatusEnum.IN_PROGRESS - session.started_at = datetime.utcnow() + session.started_at = datetime.now(timezone.utc) session.total_items = total_requirements - session.updated_at = datetime.utcnow() + session.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(session) @@ -1344,7 +1344,7 @@ class AuditSignOffRepository: if sign and signed_by: signoff.create_signature(signed_by) - signoff.updated_at = datetime.utcnow() + signoff.updated_at = datetime.now(timezone.utc) self.db.commit() self.db.refresh(signoff) @@ -1376,7 +1376,7 @@ class AuditSignOffRepository: signoff.notes = notes if sign and signed_by: signoff.create_signature(signed_by) - signoff.updated_at = datetime.utcnow() + signoff.updated_at = datetime.now(timezone.utc) else: # Create new signoff = AuditSignOffDB( @@ -1416,7 +1416,7 @@ class AuditSignOffRepository: ).first() if session: session.completed_items = completed - session.updated_at = datetime.utcnow() + session.updated_at = datetime.now(timezone.utc) self.db.commit() def get_checklist( diff --git a/backend-compliance/compliance/services/audit_pdf_generator.py b/backend-compliance/compliance/services/audit_pdf_generator.py index d35e553..cc7a9d3 100644 --- a/backend-compliance/compliance/services/audit_pdf_generator.py +++ b/backend-compliance/compliance/services/audit_pdf_generator.py @@ -16,7 +16,7 @@ Uses reportlab for PDF generation (lightweight, no external dependencies). import io import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Dict, List, Any, Optional, Tuple from sqlalchemy.orm import Session @@ -255,7 +255,7 @@ class AuditPDFGenerator: doc.build(story) # Generate filename - date_str = datetime.utcnow().strftime('%Y%m%d') + date_str = datetime.now(timezone.utc).strftime('%Y%m%d') filename = f"audit_report_{session.name.replace(' ', '_')}_{date_str}.pdf" return buffer.getvalue(), filename @@ -429,7 +429,7 @@ class AuditPDFGenerator: story.append(Spacer(1, 30*mm)) gen_label = 'Generiert am' if language == 'de' else 'Generated on' story.append(Paragraph( - f"{gen_label}: {datetime.utcnow().strftime('%d.%m.%Y %H:%M')} UTC", + f"{gen_label}: {datetime.now(timezone.utc).strftime('%d.%m.%Y %H:%M')} UTC", self.styles['Footer'] )) diff --git a/backend-compliance/compliance/services/auto_risk_updater.py b/backend-compliance/compliance/services/auto_risk_updater.py index 077b6bd..69e3bfe 100644 --- a/backend-compliance/compliance/services/auto_risk_updater.py +++ b/backend-compliance/compliance/services/auto_risk_updater.py @@ -11,7 +11,7 @@ Sprint 6: CI/CD Evidence Collection (2026-01-18) """ import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Dict, List, Optional from dataclasses import dataclass from enum import Enum @@ -140,7 +140,7 @@ class AutoRiskUpdater: if new_status != old_status: control.status = ControlStatusEnum(new_status) control.status_notes = self._generate_status_notes(scan_result) - control.updated_at = datetime.utcnow() + control.updated_at = datetime.now(timezone.utc) control_updated = True logger.info(f"Control {scan_result.control_id} status changed: {old_status} -> {new_status}") @@ -225,7 +225,7 @@ class AutoRiskUpdater: source="ci_pipeline", ci_job_id=scan_result.ci_job_id, status=EvidenceStatusEnum.VALID, - valid_from=datetime.utcnow(), + valid_from=datetime.now(timezone.utc), collected_at=scan_result.timestamp, ) @@ -298,8 +298,8 @@ class AutoRiskUpdater: risk_updated = True if risk_updated: - risk.last_assessed_at = datetime.utcnow() - risk.updated_at = datetime.utcnow() + risk.last_assessed_at = datetime.now(timezone.utc) + risk.updated_at = datetime.now(timezone.utc) affected_risks.append(risk.risk_id) logger.info(f"Updated risk {risk.risk_id} due to control {control.control_id} status change") @@ -354,7 +354,7 @@ class AutoRiskUpdater: try: ts = datetime.fromisoformat(timestamp.replace('Z', '+00:00')) except (ValueError, AttributeError): - ts = datetime.utcnow() + ts = datetime.now(timezone.utc) # Determine scan type from evidence_type scan_type = ScanType.SAST # Default diff --git a/backend-compliance/compliance/services/export_generator.py b/backend-compliance/compliance/services/export_generator.py index eeeec1f..d78cc3d 100644 --- a/backend-compliance/compliance/services/export_generator.py +++ b/backend-compliance/compliance/services/export_generator.py @@ -16,7 +16,7 @@ import os import shutil import tempfile import zipfile -from datetime import datetime, date +from datetime import datetime, date, timezone from pathlib import Path from typing import Dict, List, Optional, Any @@ -98,7 +98,7 @@ class AuditExportGenerator: export_record.file_hash = file_hash export_record.file_size_bytes = file_size export_record.status = ExportStatusEnum.COMPLETED - export_record.completed_at = datetime.utcnow() + export_record.completed_at = datetime.now(timezone.utc) # Calculate statistics stats = self._calculate_statistics( diff --git a/backend-compliance/compliance/services/regulation_scraper.py b/backend-compliance/compliance/services/regulation_scraper.py index 83d06e1..f1feb51 100644 --- a/backend-compliance/compliance/services/regulation_scraper.py +++ b/backend-compliance/compliance/services/regulation_scraper.py @@ -11,7 +11,7 @@ Similar pattern to edu-search and zeugnisse-crawler. import logging import re -from datetime import datetime +from datetime import datetime, timezone from typing import Dict, List, Any, Optional from enum import Enum @@ -198,7 +198,7 @@ class RegulationScraperService: async def scrape_all(self) -> Dict[str, Any]: """Scrape all known regulation sources.""" self.status = ScraperStatus.RUNNING - self.stats["last_run"] = datetime.utcnow().isoformat() + self.stats["last_run"] = datetime.now(timezone.utc).isoformat() results = { "success": [], diff --git a/backend-compliance/compliance/services/report_generator.py b/backend-compliance/compliance/services/report_generator.py index 2765b98..52d36dc 100644 --- a/backend-compliance/compliance/services/report_generator.py +++ b/backend-compliance/compliance/services/report_generator.py @@ -11,7 +11,7 @@ Reports include: """ import logging -from datetime import datetime, date, timedelta +from datetime import datetime, date, timedelta, timezone from typing import Dict, List, Any, Optional from enum import Enum @@ -75,7 +75,7 @@ class ComplianceReportGenerator: report = { "report_metadata": { - "generated_at": datetime.utcnow().isoformat(), + "generated_at": datetime.now(timezone.utc).isoformat(), "period": period.value, "as_of_date": as_of_date.isoformat(), "date_range_start": date_range["start"].isoformat(), @@ -415,7 +415,7 @@ class ComplianceReportGenerator: evidence_stats = self.evidence_repo.get_statistics() return { - "generated_at": datetime.utcnow().isoformat(), + "generated_at": datetime.now(timezone.utc).isoformat(), "compliance_score": stats.get("compliance_score", 0), "controls": { "total": stats.get("total", 0), diff --git a/backend-compliance/compliance/tests/test_audit_routes.py b/backend-compliance/compliance/tests/test_audit_routes.py index 5800b5c..2b2fadb 100644 --- a/backend-compliance/compliance/tests/test_audit_routes.py +++ b/backend-compliance/compliance/tests/test_audit_routes.py @@ -8,7 +8,7 @@ Run with: pytest backend/compliance/tests/test_audit_routes.py -v import pytest import hashlib -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import MagicMock from uuid import uuid4 @@ -78,7 +78,7 @@ def sample_session(): completed_items=0, compliant_count=0, non_compliant_count=0, - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -94,7 +94,7 @@ def sample_signoff(sample_session, sample_requirement): signature_hash=None, signed_at=None, signed_by=None, - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -214,7 +214,7 @@ class TestAuditSessionLifecycle: assert sample_session.status == AuditSessionStatusEnum.DRAFT sample_session.status = AuditSessionStatusEnum.IN_PROGRESS - sample_session.started_at = datetime.utcnow() + sample_session.started_at = datetime.now(timezone.utc) assert sample_session.status == AuditSessionStatusEnum.IN_PROGRESS assert sample_session.started_at is not None @@ -231,7 +231,7 @@ class TestAuditSessionLifecycle: sample_session.status = AuditSessionStatusEnum.IN_PROGRESS sample_session.status = AuditSessionStatusEnum.COMPLETED - sample_session.completed_at = datetime.utcnow() + sample_session.completed_at = datetime.now(timezone.utc) assert sample_session.status == AuditSessionStatusEnum.COMPLETED assert sample_session.completed_at is not None @@ -353,7 +353,7 @@ class TestSignOff: def test_signoff_with_signature_creates_hash(self, sample_session, sample_requirement): """Signing off with signature should create SHA-256 hash.""" result = AuditResultEnum.COMPLIANT - timestamp = datetime.utcnow().isoformat() + timestamp = datetime.now(timezone.utc).isoformat() data = f"{result.value}|{sample_requirement.id}|{sample_session.auditor_name}|{timestamp}" signature_hash = hashlib.sha256(data.encode()).hexdigest() @@ -382,7 +382,7 @@ class TestSignOff: # First sign-off should trigger auto-start sample_session.status = AuditSessionStatusEnum.IN_PROGRESS - sample_session.started_at = datetime.utcnow() + sample_session.started_at = datetime.now(timezone.utc) assert sample_session.status == AuditSessionStatusEnum.IN_PROGRESS @@ -390,7 +390,7 @@ class TestSignOff: """Updating an existing sign-off should work.""" sample_signoff.result = AuditResultEnum.NON_COMPLIANT sample_signoff.notes = "Updated: needs improvement" - sample_signoff.updated_at = datetime.utcnow() + sample_signoff.updated_at = datetime.now(timezone.utc) assert sample_signoff.result == AuditResultEnum.NON_COMPLIANT assert "Updated" in sample_signoff.notes @@ -423,7 +423,7 @@ class TestGetSignOff: # With signature sample_signoff.signature_hash = "abc123" - sample_signoff.signed_at = datetime.utcnow() + sample_signoff.signed_at = datetime.now(timezone.utc) sample_signoff.signed_by = "Test Auditor" assert sample_signoff.signature_hash == "abc123" diff --git a/backend-compliance/compliance/tests/test_auto_risk_updater.py b/backend-compliance/compliance/tests/test_auto_risk_updater.py index 7e7c43e..e9d218d 100644 --- a/backend-compliance/compliance/tests/test_auto_risk_updater.py +++ b/backend-compliance/compliance/tests/test_auto_risk_updater.py @@ -4,7 +4,7 @@ Tests for the AutoRiskUpdater Service. Sprint 6: CI/CD Evidence Collection & Automatic Risk Updates (2026-01-18) """ -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import MagicMock from ..services.auto_risk_updater import ( @@ -188,7 +188,7 @@ class TestGenerateAlerts: scan_result = ScanResult( scan_type=ScanType.DEPENDENCY, tool="Trivy", - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), commit_sha="abc123", branch="main", control_id="SDLC-002", @@ -209,7 +209,7 @@ class TestGenerateAlerts: scan_result = ScanResult( scan_type=ScanType.SAST, tool="Semgrep", - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), commit_sha="def456", branch="main", control_id="SDLC-001", @@ -228,7 +228,7 @@ class TestGenerateAlerts: scan_result = ScanResult( scan_type=ScanType.CONTAINER, tool="Trivy", - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), commit_sha="ghi789", branch="main", control_id="SDLC-006", @@ -247,7 +247,7 @@ class TestGenerateAlerts: scan_result = ScanResult( scan_type=ScanType.SAST, tool="Semgrep", - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), commit_sha="jkl012", branch="main", control_id="SDLC-001", @@ -369,7 +369,7 @@ class TestScanResult: result = ScanResult( scan_type=ScanType.DEPENDENCY, tool="Trivy", - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), commit_sha="xyz789", branch="develop", control_id="SDLC-002", diff --git a/backend-compliance/compliance/tests/test_compliance_routes.py b/backend-compliance/compliance/tests/test_compliance_routes.py index 7195101..1999ecf 100644 --- a/backend-compliance/compliance/tests/test_compliance_routes.py +++ b/backend-compliance/compliance/tests/test_compliance_routes.py @@ -8,7 +8,7 @@ Run with: pytest compliance/tests/test_compliance_routes.py -v """ import pytest -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import MagicMock from uuid import uuid4 @@ -41,8 +41,8 @@ def sample_regulation(): name="Datenschutz-Grundverordnung", full_name="Verordnung (EU) 2016/679", is_active=True, - created_at=datetime.utcnow(), - updated_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc), ) @@ -57,8 +57,8 @@ def sample_requirement(sample_regulation): description="Personenbezogene Daten duerfen nur verarbeitet werden, wenn eine Rechtsgrundlage vorliegt.", priority=4, is_applicable=True, - created_at=datetime.utcnow(), - updated_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc), ) @@ -74,8 +74,8 @@ def sample_ai_system(): classification=AIClassificationEnum.UNCLASSIFIED, status=AISystemStatusEnum.DRAFT, obligations=[], - created_at=datetime.utcnow(), - updated_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc), ) @@ -96,8 +96,8 @@ class TestCreateRequirement: description="Geeignete technische Massnahmen", priority=3, is_applicable=True, - created_at=datetime.utcnow(), - updated_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc), ) assert req.regulation_id == sample_regulation.id @@ -196,7 +196,7 @@ class TestUpdateRequirement: def test_update_audit_status_sets_audit_date(self, sample_requirement): """Updating audit_status should set last_audit_date.""" sample_requirement.audit_status = "compliant" - sample_requirement.last_audit_date = datetime.utcnow() + sample_requirement.last_audit_date = datetime.now(timezone.utc) assert sample_requirement.audit_status == "compliant" assert sample_requirement.last_audit_date is not None @@ -287,7 +287,7 @@ class TestAISystemCRUD: def test_update_ai_system_with_assessment(self, sample_ai_system): """After assessment, system should have assessment_date and result.""" - sample_ai_system.assessment_date = datetime.utcnow() + sample_ai_system.assessment_date = datetime.now(timezone.utc) sample_ai_system.assessment_result = { "overall_risk": "high", "risk_factors": [{"factor": "education sector", "severity": "high"}], diff --git a/backend-compliance/compliance/tests/test_isms_routes.py b/backend-compliance/compliance/tests/test_isms_routes.py index 01a063b..ec073b3 100644 --- a/backend-compliance/compliance/tests/test_isms_routes.py +++ b/backend-compliance/compliance/tests/test_isms_routes.py @@ -15,7 +15,7 @@ Run with: pytest backend/compliance/tests/test_isms_routes.py -v """ import pytest -from datetime import datetime, date +from datetime import datetime, date, timezone from unittest.mock import MagicMock from uuid import uuid4 @@ -56,7 +56,7 @@ def sample_scope(): status=ApprovalStatusEnum.DRAFT, version="1.0", created_by="admin@breakpilot.de", - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -65,7 +65,7 @@ def sample_approved_scope(sample_scope): """Create an approved ISMS scope for testing.""" sample_scope.status = ApprovalStatusEnum.APPROVED sample_scope.approved_by = "ceo@breakpilot.de" - sample_scope.approved_at = datetime.utcnow() + sample_scope.approved_at = datetime.now(timezone.utc) sample_scope.effective_date = date.today() sample_scope.review_date = date(date.today().year + 1, date.today().month, date.today().day) sample_scope.approval_signature = "sha256_signature_hash" @@ -88,7 +88,7 @@ def sample_policy(): authored_by="iso@breakpilot.de", status=ApprovalStatusEnum.DRAFT, version="1.0", - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -116,7 +116,7 @@ def sample_objective(): related_controls=["OPS-003"], status="active", progress_percentage=0.0, - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -136,7 +136,7 @@ def sample_soa_entry(): coverage_level="full", evidence_description="ISMS Policy v2.0, signed by CEO", version="1.0", - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -158,7 +158,7 @@ def sample_finding(): identified_date=date.today(), due_date=date(2026, 3, 31), status=FindingStatusEnum.OPEN, - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -178,7 +178,7 @@ def sample_major_finding(): identified_date=date.today(), due_date=date(2026, 2, 28), status=FindingStatusEnum.OPEN, - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -198,7 +198,7 @@ def sample_capa(sample_finding): planned_completion=date(2026, 2, 15), effectiveness_criteria="Document approved and distributed to audit team", status="planned", - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -219,7 +219,7 @@ def sample_management_review(): {"name": "ISO", "role": "ISMS Manager"}, ], status="draft", - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -239,7 +239,7 @@ def sample_internal_audit(): lead_auditor="internal.auditor@breakpilot.de", audit_team=["internal.auditor@breakpilot.de", "qa@breakpilot.de"], status="planned", - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) @@ -502,7 +502,7 @@ class TestISMSReadinessCheck: """Readiness check should identify potential major findings.""" check = ISMSReadinessCheckDB( id=str(uuid4()), - check_date=datetime.utcnow(), + check_date=datetime.now(timezone.utc), triggered_by="admin@breakpilot.de", overall_status="not_ready", certification_possible=False, @@ -532,7 +532,7 @@ class TestISMSReadinessCheck: """Readiness check should show status for each ISO chapter.""" check = ISMSReadinessCheckDB( id=str(uuid4()), - check_date=datetime.utcnow(), + check_date=datetime.now(timezone.utc), triggered_by="admin@breakpilot.de", overall_status="ready", certification_possible=True, @@ -606,7 +606,7 @@ class TestAuditTrail: entity_name="ISMS Scope v1.0", action="approve", performed_by="ceo@breakpilot.de", - performed_at=datetime.utcnow(), + performed_at=datetime.now(timezone.utc), checksum="sha256_hash", ) @@ -630,7 +630,7 @@ class TestAuditTrail: new_value="approved", change_summary="Policy approved by CEO", performed_by="ceo@breakpilot.de", - performed_at=datetime.utcnow(), + performed_at=datetime.now(timezone.utc), checksum="sha256_hash", ) diff --git a/backend-compliance/consent_client.py b/backend-compliance/consent_client.py index 2bf1daf..d558a81 100644 --- a/backend-compliance/consent_client.py +++ b/backend-compliance/consent_client.py @@ -5,7 +5,7 @@ Kommuniziert mit dem Consent Management Service für GDPR-Compliance import httpx import jwt -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Optional, List, Dict, Any from dataclasses import dataclass from enum import Enum @@ -44,8 +44,8 @@ def generate_jwt_token( "user_id": user_id, "email": email, "role": role, - "exp": datetime.utcnow() + timedelta(hours=expires_hours), - "iat": datetime.utcnow(), + "exp": datetime.now(timezone.utc) + timedelta(hours=expires_hours), + "iat": datetime.now(timezone.utc), } return jwt.encode(payload, JWT_SECRET, algorithm="HS256") diff --git a/backend-compliance/tests/test_dsfa_routes.py b/backend-compliance/tests/test_dsfa_routes.py index 6b78d78..6601ced 100644 --- a/backend-compliance/tests/test_dsfa_routes.py +++ b/backend-compliance/tests/test_dsfa_routes.py @@ -10,7 +10,7 @@ import pytest import uuid import os import sys -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import MagicMock from fastapi import FastAPI @@ -51,7 +51,7 @@ _RawSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) @event.listens_for(engine, "connect") def _register_sqlite_functions(dbapi_conn, connection_record): """Register PostgreSQL-compatible functions for SQLite.""" - dbapi_conn.create_function("NOW", 0, lambda: datetime.utcnow().isoformat()) + dbapi_conn.create_function("NOW", 0, lambda: datetime.now(timezone.utc).isoformat()) TENANT_ID = "default" diff --git a/backend-compliance/tests/test_dsr_routes.py b/backend-compliance/tests/test_dsr_routes.py index ff91e9d..bd9748b 100644 --- a/backend-compliance/tests/test_dsr_routes.py +++ b/backend-compliance/tests/test_dsr_routes.py @@ -6,7 +6,7 @@ Pattern: app.dependency_overrides[get_db] for FastAPI DI. import uuid import os import sys -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import pytest from fastapi import FastAPI @@ -75,7 +75,7 @@ def db_session(): def _create_dsr_in_db(db, **kwargs): """Helper to create a DSR directly in DB.""" - now = datetime.utcnow() + now = datetime.now(timezone.utc) defaults = { "tenant_id": uuid.UUID(TENANT_ID), "request_number": f"DSR-2026-{str(uuid.uuid4())[:6].upper()}", @@ -241,8 +241,8 @@ class TestListDSR: assert len(data["requests"]) == 2 def test_list_overdue_only(self, db_session): - _create_dsr_in_db(db_session, deadline_at=datetime.utcnow() - timedelta(days=5), status="processing") - _create_dsr_in_db(db_session, deadline_at=datetime.utcnow() + timedelta(days=20), status="processing") + _create_dsr_in_db(db_session, deadline_at=datetime.now(timezone.utc) - timedelta(days=5), status="processing") + _create_dsr_in_db(db_session, deadline_at=datetime.now(timezone.utc) + timedelta(days=20), status="processing") resp = client.get("/api/compliance/dsr?overdue_only=true", headers=HEADERS) assert resp.status_code == 200 @@ -339,7 +339,7 @@ class TestDSRStats: _create_dsr_in_db(db_session, status="intake", request_type="access") _create_dsr_in_db(db_session, status="processing", request_type="erasure") _create_dsr_in_db(db_session, status="completed", request_type="access", - completed_at=datetime.utcnow()) + completed_at=datetime.now(timezone.utc)) resp = client.get("/api/compliance/dsr/stats", headers=HEADERS) assert resp.status_code == 200 @@ -561,9 +561,9 @@ class TestDeadlineProcessing: def test_process_deadlines_with_overdue(self, db_session): _create_dsr_in_db(db_session, status="processing", - deadline_at=datetime.utcnow() - timedelta(days=5)) + deadline_at=datetime.now(timezone.utc) - timedelta(days=5)) _create_dsr_in_db(db_session, status="processing", - deadline_at=datetime.utcnow() + timedelta(days=20)) + deadline_at=datetime.now(timezone.utc) + timedelta(days=20)) resp = client.post("/api/compliance/dsr/deadlines/process", headers=HEADERS) assert resp.status_code == 200 @@ -609,7 +609,7 @@ class TestDSRTemplates: subject="Bestaetigung", body_html="
Test
", status="published", - published_at=datetime.utcnow(), + published_at=datetime.now(timezone.utc), ) db_session.add(v) db_session.commit() diff --git a/backend-compliance/tests/test_einwilligungen_routes.py b/backend-compliance/tests/test_einwilligungen_routes.py index 5af913d..ffc4c18 100644 --- a/backend-compliance/tests/test_einwilligungen_routes.py +++ b/backend-compliance/tests/test_einwilligungen_routes.py @@ -7,7 +7,7 @@ Consent widerrufen, Statistiken. import pytest from unittest.mock import MagicMock, patch -from datetime import datetime +from datetime import datetime, timezone import uuid @@ -25,7 +25,7 @@ def make_catalog(tenant_id='test-tenant'): rec.tenant_id = tenant_id rec.selected_data_point_ids = ['dp-001', 'dp-002'] rec.custom_data_points = [] - rec.updated_at = datetime.utcnow() + rec.updated_at = datetime.now(timezone.utc) return rec @@ -34,7 +34,7 @@ def make_company(tenant_id='test-tenant'): rec.id = uuid.uuid4() rec.tenant_id = tenant_id rec.data = {'company_name': 'Test GmbH', 'email': 'datenschutz@test.de'} - rec.updated_at = datetime.utcnow() + rec.updated_at = datetime.now(timezone.utc) return rec @@ -47,7 +47,7 @@ def make_cookies(tenant_id='test-tenant'): {'id': 'analytics', 'name': 'Analyse', 'isRequired': False, 'defaultEnabled': False}, ] rec.config = {'position': 'bottom', 'style': 'bar'} - rec.updated_at = datetime.utcnow() + rec.updated_at = datetime.now(timezone.utc) return rec @@ -58,13 +58,13 @@ def make_consent(tenant_id='test-tenant', user_id='user-001', data_point_id='dp- rec.user_id = user_id rec.data_point_id = data_point_id rec.granted = granted - rec.granted_at = datetime.utcnow() + rec.granted_at = datetime.now(timezone.utc) rec.revoked_at = None rec.consent_version = '1.0' rec.source = 'website' rec.ip_address = None rec.user_agent = None - rec.created_at = datetime.utcnow() + rec.created_at = datetime.now(timezone.utc) return rec @@ -263,7 +263,7 @@ class TestConsentDB: user_id='user-001', data_point_id='dp-marketing', granted=True, - granted_at=datetime.utcnow(), + granted_at=datetime.now(timezone.utc), consent_version='1.0', source='website', ) @@ -276,13 +276,13 @@ class TestConsentDB: consent = make_consent() assert consent.revoked_at is None - consent.revoked_at = datetime.utcnow() + consent.revoked_at = datetime.now(timezone.utc) assert consent.revoked_at is not None def test_cannot_revoke_already_revoked(self): """Should not be possible to revoke an already revoked consent.""" consent = make_consent() - consent.revoked_at = datetime.utcnow() + consent.revoked_at = datetime.now(timezone.utc) # Simulate the guard logic from the route already_revoked = consent.revoked_at is not None @@ -315,7 +315,7 @@ class TestConsentStats: make_consent(user_id='user-2', data_point_id='dp-1', granted=True), ] # Revoke one - consents[1].revoked_at = datetime.utcnow() + consents[1].revoked_at = datetime.now(timezone.utc) total = len(consents) active = sum(1 for c in consents if c.granted and not c.revoked_at) @@ -334,7 +334,7 @@ class TestConsentStats: make_consent(user_id='user-2', granted=True), make_consent(user_id='user-3', granted=True), ] - consents[2].revoked_at = datetime.utcnow() # user-3 revoked + consents[2].revoked_at = datetime.now(timezone.utc) # user-3 revoked unique_users = len(set(c.user_id for c in consents)) users_with_active = len(set(c.user_id for c in consents if c.granted and not c.revoked_at)) @@ -501,7 +501,7 @@ class TestConsentHistoryTracking: from compliance.db.einwilligungen_models import EinwilligungenConsentHistoryDB consent = make_consent() - consent.revoked_at = datetime.utcnow() + consent.revoked_at = datetime.now(timezone.utc) entry = EinwilligungenConsentHistoryDB( consent_id=consent.id, tenant_id=consent.tenant_id, @@ -516,7 +516,7 @@ class TestConsentHistoryTracking: entry_id = _uuid.uuid4() consent_id = _uuid.uuid4() - now = datetime.utcnow() + now = datetime.now(timezone.utc) row = { "id": str(entry_id), diff --git a/backend-compliance/tests/test_isms_routes.py b/backend-compliance/tests/test_isms_routes.py index 8eb9364..89aa7d2 100644 --- a/backend-compliance/tests/test_isms_routes.py +++ b/backend-compliance/tests/test_isms_routes.py @@ -13,7 +13,7 @@ Run with: cd backend-compliance && python3 -m pytest tests/test_isms_routes.py - import os import sys import pytest -from datetime import date, datetime +from datetime import date, datetime, timezone from fastapi import FastAPI from fastapi.testclient import TestClient @@ -40,7 +40,7 @@ def _set_sqlite_pragma(dbapi_conn, connection_record): cursor = dbapi_conn.cursor() cursor.execute("PRAGMA foreign_keys=ON") cursor.close() - dbapi_conn.create_function("NOW", 0, lambda: datetime.utcnow().isoformat()) + dbapi_conn.create_function("NOW", 0, lambda: datetime.now(timezone.utc).isoformat()) app = FastAPI() diff --git a/backend-compliance/tests/test_legal_document_routes.py b/backend-compliance/tests/test_legal_document_routes.py index e51c148..0b4e4cb 100644 --- a/backend-compliance/tests/test_legal_document_routes.py +++ b/backend-compliance/tests/test_legal_document_routes.py @@ -7,7 +7,7 @@ Rejection-Flow, approval history. import pytest from unittest.mock import MagicMock, patch -from datetime import datetime +from datetime import datetime, timezone import uuid @@ -27,7 +27,7 @@ def make_document(type='privacy_policy', name='Datenschutzerklärung', tenant_id doc.name = name doc.description = 'Test description' doc.mandatory = False - doc.created_at = datetime.utcnow() + doc.created_at = datetime.now(timezone.utc) doc.updated_at = None return doc @@ -46,7 +46,7 @@ def make_version(document_id=None, version='1.0', status='draft', title='Test Ve v.approved_by = None v.approved_at = None v.rejection_reason = None - v.created_at = datetime.utcnow() + v.created_at = datetime.now(timezone.utc) v.updated_at = None return v @@ -58,7 +58,7 @@ def make_approval(version_id=None, action='created'): a.action = action a.approver = 'admin@test.de' a.comment = None - a.created_at = datetime.utcnow() + a.created_at = datetime.now(timezone.utc) return a @@ -179,7 +179,7 @@ class TestVersionToResponse: from compliance.api.legal_document_routes import _version_to_response v = make_version(status='approved') v.approved_by = 'dpo@company.de' - v.approved_at = datetime.utcnow() + v.approved_at = datetime.now(timezone.utc) resp = _version_to_response(v) assert resp.status == 'approved' assert resp.approved_by == 'dpo@company.de' @@ -254,7 +254,7 @@ class TestApprovalWorkflow: # Step 2: Approve mock_db.reset_mock() _transition(mock_db, str(v.id), ['review'], 'approved', 'approved', 'dpo', 'Korrekt', - extra_updates={'approved_by': 'dpo', 'approved_at': datetime.utcnow()}) + extra_updates={'approved_by': 'dpo', 'approved_at': datetime.now(timezone.utc)}) assert v.status == 'approved' # Step 3: Publish diff --git a/backend-compliance/tests/test_legal_document_routes_extended.py b/backend-compliance/tests/test_legal_document_routes_extended.py index 764f72a..aad27da 100644 --- a/backend-compliance/tests/test_legal_document_routes_extended.py +++ b/backend-compliance/tests/test_legal_document_routes_extended.py @@ -5,7 +5,7 @@ Tests for Legal Document extended routes (User Consents, Audit Log, Cookie Categ import uuid import os import sys -from datetime import datetime +from datetime import datetime, timezone import pytest from fastapi import FastAPI @@ -103,7 +103,7 @@ def _publish_version(version_id): v = db.query(LegalDocumentVersionDB).filter(LegalDocumentVersionDB.id == vid).first() v.status = "published" v.approved_by = "admin" - v.approved_at = datetime.utcnow() + v.approved_at = datetime.now(timezone.utc) db.commit() db.refresh(v) result = {"id": str(v.id), "status": v.status} diff --git a/backend-compliance/tests/test_vendor_compliance_routes.py b/backend-compliance/tests/test_vendor_compliance_routes.py index 5d8b327..7a34692 100644 --- a/backend-compliance/tests/test_vendor_compliance_routes.py +++ b/backend-compliance/tests/test_vendor_compliance_routes.py @@ -15,7 +15,7 @@ import pytest import uuid import os import sys -from datetime import datetime +from datetime import datetime, timezone from fastapi import FastAPI from fastapi.testclient import TestClient @@ -40,7 +40,7 @@ TENANT_ID = "default" @event.listens_for(engine, "connect") def _register_sqlite_functions(dbapi_conn, connection_record): - dbapi_conn.create_function("NOW", 0, lambda: datetime.utcnow().isoformat()) + dbapi_conn.create_function("NOW", 0, lambda: datetime.now(timezone.utc).isoformat()) class _DictRow(dict): diff --git a/backend-compliance/tests/test_vvt_routes.py b/backend-compliance/tests/test_vvt_routes.py index c9461b3..9031016 100644 --- a/backend-compliance/tests/test_vvt_routes.py +++ b/backend-compliance/tests/test_vvt_routes.py @@ -186,7 +186,7 @@ class TestActivityToResponse: act.next_review_at = kwargs.get("next_review_at", None) act.created_by = kwargs.get("created_by", None) act.dsfa_id = kwargs.get("dsfa_id", None) - act.created_at = datetime.utcnow() + act.created_at = datetime.now(timezone.utc) act.updated_at = None return act @@ -330,7 +330,7 @@ class TestVVTConsolidationResponse: act.next_review_at = kwargs.get("next_review_at", None) act.created_by = kwargs.get("created_by", None) act.dsfa_id = kwargs.get("dsfa_id", None) - act.created_at = datetime.utcnow() + act.created_at = datetime.now(timezone.utc) act.updated_at = None return act diff --git a/backend-compliance/tests/test_vvt_tenant_isolation.py b/backend-compliance/tests/test_vvt_tenant_isolation.py index e7960d3..9167cca 100644 --- a/backend-compliance/tests/test_vvt_tenant_isolation.py +++ b/backend-compliance/tests/test_vvt_tenant_isolation.py @@ -10,7 +10,7 @@ Verifies that: import pytest import uuid from unittest.mock import MagicMock, AsyncMock, patch -from datetime import datetime +from datetime import datetime, timezone from fastapi import HTTPException from fastapi.testclient import TestClient @@ -144,8 +144,8 @@ def _make_activity(tenant_id, vvt_id="VVT-001", name="Test", **kwargs): act.next_review_at = None act.created_by = "system" act.dsfa_id = None - act.created_at = datetime.utcnow() - act.updated_at = datetime.utcnow() + act.created_at = datetime.now(timezone.utc) + act.updated_at = datetime.now(timezone.utc) return act