freeleaps-ops/apps/gitea-webhook-ambassador-python/app/handlers/admin.py

287 lines
8.5 KiB
Python

"""
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)}")