diff --git a/src/agents/orchestrator.py b/src/agents/orchestrator.py index a47ad26..fac03fa 100644 --- a/src/agents/orchestrator.py +++ b/src/agents/orchestrator.py @@ -3,7 +3,7 @@ import asyncio import json import logging import re -from datetime import datetime +from datetime import datetime, timezone from config import TIMEZONE from typing import Optional from urllib.parse import urlparse, urlunparse @@ -206,7 +206,7 @@ async def _create_notifications_for_incident( if not notifications: return - now = datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S') + now = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S') if visibility == "public" and tenant_id: cursor = await db.execute( @@ -465,7 +465,7 @@ class AgentOrchestrator: await db.execute( """UPDATE refresh_log SET status = 'cancelled', error_message = 'Vom Nutzer abgebrochen', completed_at = ? WHERE incident_id = ? AND status = 'running'""", - (datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S'), incident_id), + (datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S'), incident_id), ) await db.commit() except Exception as e: @@ -481,7 +481,7 @@ class AgentOrchestrator: await db.execute( """UPDATE refresh_log SET status = 'error', error_message = ?, completed_at = ? WHERE incident_id = ? AND status = 'running'""", - (error[:500], datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S'), incident_id), + (error[:500], datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S'), incident_id), ) await db.commit() except Exception as e: @@ -542,12 +542,12 @@ class AgentOrchestrator: await db.execute( """UPDATE refresh_log SET status = 'error', error_message = 'Retry gestartet', completed_at = ? WHERE incident_id = ? AND status = 'running'""", - (datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S'), incident_id), + (datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S'), incident_id), ) await db.commit() # Refresh-Log starten - now = datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S') + now = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S') cursor = await db.execute( "INSERT INTO refresh_log (incident_id, started_at, status, trigger_type, retry_count, tenant_id) VALUES (?, ?, 'running', ?, ?, ?)", (incident_id, now, trigger_type, retry_count, tenant_id), @@ -1008,7 +1008,7 @@ class AgentOrchestrator: cache_creation_tokens = ?, cache_read_tokens = ?, total_cost_usd = ?, api_calls = ? WHERE id = ?""", - (datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S'), new_count, + (datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S'), new_count, usage_acc.input_tokens, usage_acc.output_tokens, usage_acc.cache_creation_tokens, usage_acc.cache_read_tokens, round(usage_acc.total_cost_usd, 7), usage_acc.call_count, log_id), diff --git a/src/auth.py b/src/auth.py index 233f08b..0215172 100644 --- a/src/auth.py +++ b/src/auth.py @@ -1,7 +1,7 @@ """JWT-Authentifizierung mit Magic-Link-Support und Multi-Tenancy.""" import secrets import string -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from jose import jwt, JWTError from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials @@ -23,7 +23,7 @@ def create_token( org_slug: str = None, ) -> str: """JWT-Token erstellen mit Tenant-Kontext.""" - now = datetime.now(TIMEZONE) + now = datetime.now(timezone.utc) expire = now + timedelta(hours=JWT_EXPIRE_HOURS) payload = { "sub": str(user_id), diff --git a/src/main.py b/src/main.py index 493cb88..1821d23 100644 --- a/src/main.py +++ b/src/main.py @@ -5,7 +5,7 @@ import logging import os import sys from contextlib import asynccontextmanager -from datetime import datetime +from datetime import datetime, timezone from typing import Dict from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, Request, Response @@ -93,7 +93,7 @@ async def check_auto_refresh(): ) incidents = await cursor.fetchall() - now = datetime.now(TIMEZONE) + now = datetime.now(timezone.utc) for incident in incidents: incident_id = incident["id"] @@ -112,7 +112,7 @@ async def check_auto_refresh(): else: last_time = datetime.fromisoformat(last_refresh["completed_at"]) if last_time.tzinfo is None: - last_time = last_time.replace(tzinfo=TIMEZONE) + last_time = last_time.replace(tzinfo=timezone.utc) elapsed = (now - last_time).total_seconds() / 60 if elapsed >= interval: should_refresh = True @@ -143,11 +143,11 @@ async def cleanup_expired(): ) incidents = await cursor.fetchall() - now = datetime.now(TIMEZONE) + now = datetime.now(timezone.utc) for incident in incidents: created = datetime.fromisoformat(incident["created_at"]) if created.tzinfo is None: - created = created.replace(tzinfo=TIMEZONE) + created = created.replace(tzinfo=timezone.utc) age_days = (now - created).days if age_days >= incident["retention_days"]: await db.execute( @@ -164,7 +164,7 @@ async def cleanup_expired(): for orphan in orphans: started = datetime.fromisoformat(orphan["started_at"]) if started.tzinfo is None: - started = started.replace(tzinfo=TIMEZONE) + started = started.replace(tzinfo=timezone.utc) age_minutes = (now - started).total_seconds() / 60 if age_minutes >= 15: await db.execute( @@ -195,7 +195,7 @@ async def lifespan(app: FastAPI): try: result = await db.execute( "UPDATE refresh_log SET status = 'error', completed_at = ?, error_message = 'Verwaist (Neustart, automatisch bereinigt)' WHERE status = 'running'", - (datetime.now(TIMEZONE).isoformat(),), + (datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S'),), ) if result.rowcount > 0: await db.commit() diff --git a/src/routers/auth.py b/src/routers/auth.py index 6116346..d61b625 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -1,6 +1,6 @@ """Auth-Router: Magic-Link-Login und Nutzerverwaltung.""" import logging -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, HTTPException, Request, status from models import ( MagicLinkRequest, @@ -78,7 +78,7 @@ async def request_magic_link( # Token + Code generieren token = generate_magic_token() code = generate_magic_code() - expires_at = (datetime.now(TIMEZONE) + timedelta(minutes=MAGIC_LINK_EXPIRE_MINUTES)).strftime('%Y-%m-%d %H:%M:%S') + expires_at = (datetime.now(timezone.utc) + timedelta(minutes=MAGIC_LINK_EXPIRE_MINUTES)).strftime('%Y-%m-%d %H:%M:%S') # Alte ungenutzte Magic Links fuer diese E-Mail invalidieren await db.execute( @@ -124,10 +124,10 @@ async def verify_magic_link( raise HTTPException(status_code=400, detail="Ungueltiger oder bereits verwendeter Link") # Ablauf pruefen - now = datetime.now(TIMEZONE) + now = datetime.now(timezone.utc) expires = datetime.fromisoformat(ml["expires_at"]) if expires.tzinfo is None: - expires = expires.replace(tzinfo=TIMEZONE) + expires = expires.replace(tzinfo=timezone.utc) if now > expires: raise HTTPException(status_code=400, detail="Link abgelaufen. Bitte neuen Link anfordern.") @@ -200,10 +200,10 @@ async def verify_magic_code( raise HTTPException(status_code=400, detail="Ungueltiger Code") # Ablauf pruefen - now = datetime.now(TIMEZONE) + now = datetime.now(timezone.utc) expires = datetime.fromisoformat(ml["expires_at"]) if expires.tzinfo is None: - expires = expires.replace(tzinfo=TIMEZONE) + expires = expires.replace(tzinfo=timezone.utc) if now > expires: raise HTTPException(status_code=400, detail="Code abgelaufen. Bitte neuen Code anfordern.") diff --git a/src/routers/incidents.py b/src/routers/incidents.py index 6a9c0a6..129fd93 100644 --- a/src/routers/incidents.py +++ b/src/routers/incidents.py @@ -5,7 +5,7 @@ from models import IncidentCreate, IncidentUpdate, IncidentResponse, Subscriptio from auth import get_current_user from middleware.license_check import require_writable_license from database import db_dependency, get_db -from datetime import datetime +from datetime import datetime, timezone from config import TIMEZONE import asyncio import aiosqlite @@ -102,7 +102,7 @@ async def create_incident( ): """Neue Lage anlegen.""" tenant_id = current_user.get("tenant_id") - now = datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S') + now = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S') cursor = await db.execute( """INSERT INTO incidents (title, description, type, refresh_mode, refresh_interval, retention_days, international_sources, visibility, @@ -184,7 +184,7 @@ async def update_incident( if not updates: return await _enrich_incident(db, row) - updates["updated_at"] = datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S') + updates["updated_at"] = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S') set_clause = ", ".join(f"{k} = ?" for k in updates) values = list(updates.values()) + [incident_id] @@ -667,7 +667,7 @@ def _build_markdown_export( lines.append(snap_summary) lines.append("") - now = datetime.now(TIMEZONE).strftime("%Y-%m-%d %H:%M UTC") + now = datetime.now(TIMEZONE).strftime("%Y-%m-%d %H:%M Uhr") lines.append("---") lines.append(f"*Exportiert am {now} aus AegisSight Monitor*") return "\n".join(lines) diff --git a/src/services/license_service.py b/src/services/license_service.py index e292147..ce851bf 100644 --- a/src/services/license_service.py +++ b/src/services/license_service.py @@ -1,6 +1,6 @@ """Lizenz-Verwaltung und -Pruefung.""" import logging -from datetime import datetime +from datetime import datetime, timezone from config import TIMEZONE import aiosqlite @@ -38,14 +38,14 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict: return {"valid": False, "status": "no_license", "read_only": True, "message": "Keine aktive Lizenz"} # Ablauf pruefen - now = datetime.now(TIMEZONE) + now = datetime.now(timezone.utc) valid_until = license_row["valid_until"] if valid_until is not None: try: expiry = datetime.fromisoformat(valid_until) if expiry.tzinfo is None: - expiry = expiry.replace(tzinfo=TIMEZONE) + expiry = expiry.replace(tzinfo=timezone.utc) if now > expiry: return { "valid": False,