""" Admin API handler Provides project mapping and API key management features """ import secrets from datetime import datetime, timedelta from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from sqlalchemy.orm import Session from app.database import get_db from app.models.api_key import APIKey from app.models.project_mapping import ProjectMapping from app.auth import get_current_user router = APIRouter(prefix="/api/admin", tags=["admin"]) # API key related models class APIKeyResponse(BaseModel): id: int name: str key_prefix: str created_at: datetime last_used: datetime is_active: bool class Config: from_attributes = True class CreateAPIKeyRequest(BaseModel): name: str class CreateAPIKeyResponse(BaseModel): id: int name: str key: str created_at: datetime # Project mapping related models class ProjectMappingRequest(BaseModel): repository_name: str default_job: str branch_jobs: Optional[List[dict]] = [] branch_patterns: Optional[List[dict]] = [] class ProjectMappingResponse(BaseModel): id: int repository_name: str default_job: str branch_jobs: List[dict] branch_patterns: List[dict] created_at: datetime updated_at: datetime class Config: from_attributes = True # API key management endpoints @router.get("/api-keys", response_model=List[APIKeyResponse]) async def list_api_keys( db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """List all API keys""" try: api_keys = db.query(APIKey).order_by(APIKey.created_at.desc()).all() return api_keys except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to list API keys: {str(e)}") @router.post("/api-keys", response_model=CreateAPIKeyResponse) async def create_api_key( request: CreateAPIKeyRequest, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Create a new API key""" try: # Generate API key api_key = secrets.token_urlsafe(32) key_prefix = api_key[:8] # Show first 8 characters as prefix # Create database record db_api_key = APIKey( name=request.name, key_hash=api_key, # Should be hashed in production key_prefix=key_prefix, created_at=datetime.utcnow(), last_used=datetime.utcnow(), is_active=True ) db.add(db_api_key) db.commit() db.refresh(db_api_key) return CreateAPIKeyResponse( id=db_api_key.id, name=db_api_key.name, key=api_key, # Only return full key on creation created_at=db_api_key.created_at ) except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=f"Failed to create API key: {str(e)}") @router.delete("/api-keys/{key_id}") async def delete_api_key( key_id: int, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Delete API key""" try: api_key = db.query(APIKey).filter(APIKey.id == key_id).first() if not api_key: raise HTTPException(status_code=404, detail="API key not found") db.delete(api_key) db.commit() return {"message": "API key deleted successfully"} except HTTPException: raise except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=f"Failed to delete API key: {str(e)}") @router.post("/api-keys/{key_id}/revoke") async def revoke_api_key( key_id: int, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Revoke API key""" try: api_key = db.query(APIKey).filter(APIKey.id == key_id).first() if not api_key: raise HTTPException(status_code=404, detail="API key not found") api_key.is_active = False db.commit() return {"message": "API key revoked successfully"} except HTTPException: raise except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=f"Failed to revoke API key: {str(e)}") # Project mapping management endpoints @router.get("/projects", response_model=List[ProjectMappingResponse]) async def list_project_mappings( db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """List all project mappings""" try: mappings = db.query(ProjectMapping).order_by(ProjectMapping.created_at.desc()).all() return mappings except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to list project mappings: {str(e)}") @router.post("/projects", response_model=ProjectMappingResponse) async def create_project_mapping( request: ProjectMappingRequest, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Create project mapping""" try: # Check if already exists existing = db.query(ProjectMapping).filter( ProjectMapping.repository_name == request.repository_name ).first() if existing: raise HTTPException(status_code=400, detail="Project mapping already exists") # Create new mapping mapping = ProjectMapping( repository_name=request.repository_name, default_job=request.default_job, branch_jobs=request.branch_jobs or [], branch_patterns=request.branch_patterns or [], created_at=datetime.utcnow(), updated_at=datetime.utcnow() ) db.add(mapping) db.commit() db.refresh(mapping) return mapping except HTTPException: raise except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=f"Failed to create project mapping: {str(e)}") @router.get("/projects/{repository_name}", response_model=ProjectMappingResponse) async def get_project_mapping( repository_name: str, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Get project mapping""" try: mapping = db.query(ProjectMapping).filter( ProjectMapping.repository_name == repository_name ).first() if not mapping: raise HTTPException(status_code=404, detail="Project mapping not found") return mapping except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to get project mapping: {str(e)}") @router.delete("/projects/{repository_name}") async def delete_project_mapping( repository_name: str, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Delete project mapping""" try: mapping = db.query(ProjectMapping).filter( ProjectMapping.repository_name == repository_name ).first() if not mapping: raise HTTPException(status_code=404, detail="Project mapping not found") db.delete(mapping) db.commit() return {"message": "Project mapping deleted successfully"} except HTTPException: raise except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=f"Failed to delete project mapping: {str(e)}") # Statistics endpoint @router.get("/stats") async def get_admin_stats( db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """Get admin statistics""" try: # API key statistics total_keys = db.query(APIKey).count() active_keys = db.query(APIKey).filter(APIKey.is_active == True).count() # Recently used keys recent_keys = db.query(APIKey).filter( APIKey.last_used >= datetime.utcnow() - timedelta(days=7) ).count() # Project mapping statistics total_mappings = db.query(ProjectMapping).count() return { "api_keys": { "total": total_keys, "active": active_keys, "recently_used": recent_keys }, "project_mappings": { "total": total_mappings } } except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to get admin stats: {str(e)}")