import json import secrets import string from datetime import datetime, timezone from typing import Optional from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from core.config import utcnow from core.database import Keypass, get_db from core.dependencies import require_manager from core.schemas import KeypassCreate, KeypassPatch, KeypassResponse, keypass_to_response router = APIRouter(prefix="/api/admin/keypasses", tags=["admin-keypasses"]) def _generate_code(length: int = 12) -> str: alphabet = string.ascii_uppercase + string.digits return "".join(secrets.choice(alphabet) for _ in range(length)) @router.get("", response_model=list[KeypassResponse]) async def list_keypasses( db: Session = Depends(get_db), _: dict = Depends(require_manager) ): return [keypass_to_response(kp) for kp in db.query(Keypass).order_by(Keypass.created_at.desc()).all()] @router.post("", response_model=KeypassResponse, status_code=201) async def create_keypass( req: KeypassCreate, db: Session = Depends(get_db), _: dict = Depends(require_manager), ): code = req.code.strip().upper() if req.code and req.code.strip() else _generate_code() if db.query(Keypass).filter(Keypass.code == code).first(): raise HTTPException(409, "A keypass with this code already exists") kp = Keypass( code=code, description=req.description, created_at=utcnow(), expires_at=req.expires_at, revoked=False, allowed_gates=json.dumps(req.gate_ids) if req.gate_ids else None, ) db.add(kp) db.commit() db.refresh(kp) return keypass_to_response(kp) @router.patch("/{kp_id}", response_model=KeypassResponse) async def update_keypass( kp_id: int, req: KeypassPatch, db: Session = Depends(get_db), _: dict = Depends(require_manager), ): kp: Optional[Keypass] = db.query(Keypass).filter(Keypass.id == kp_id).first() if not kp: raise HTTPException(404, "Keypass not found") if kp.revoked: raise HTTPException(409, "Revoked keypasses cannot be edited") if req.description is not None: kp.description = req.description.strip() kp.expires_at = req.expires_at if req.gate_ids is not None: kp.allowed_gates = json.dumps(req.gate_ids) if req.gate_ids else None db.commit() db.refresh(kp) return keypass_to_response(kp) @router.delete("/{kp_id}", status_code=204) async def revoke_keypass( kp_id: int, db: Session = Depends(get_db), _: dict = Depends(require_manager), ): kp: Optional[Keypass] = db.query(Keypass).filter(Keypass.id == kp_id).first() if not kp: raise HTTPException(404, "Keypass not found") if kp.expires_at is not None and kp.expires_at < utcnow(): raise HTTPException(409, "Expired keypasses cannot be revoked") if kp.revoked: raise HTTPException(409, "Keypass is already revoked") kp.revoked = True kp.revoked_at = utcnow() db.commit()