import os import secrets import httpx from fastapi import FastAPI, Depends, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.orm import Session from database import get_db import crud, schemas from models import APIKey as APIKeyModel app = FastAPI(title="Ollama Proxy Admin API") ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "http://localhost:5173").split(",") ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") if not ADMIN_PASSWORD: raise RuntimeError("ADMIN_PASSWORD environment variable must be set") app.add_middleware( CORSMiddleware, allow_origins=ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"], allow_headers=["Authorization", "Content-Type"], ) def require_admin_auth(request: Request): auth = request.headers.get("Authorization", "") token = auth.removeprefix("Bearer ").strip() if not secrets.compare_digest(token, ADMIN_PASSWORD): raise HTTPException(status_code=401, detail="Invalid admin password") @app.get("/api/api-keys", response_model=list[schemas.APIKey]) async def read_api_keys( skip: int = 0, limit: int = 100, db: Session = Depends(get_db), _ = Depends(require_admin_auth), ): return db.query(APIKeyModel).offset(skip).limit(limit).all() @app.post("/api/api-keys", response_model=schemas.APIKeyCreated) async def create_api_key( api_key: schemas.APIKeyCreate, db: Session = Depends(get_db), _ = Depends(require_admin_auth), ): db_key, raw_key = crud.create_api_key( db, name=api_key.name, expires_at=api_key.expires_at, daily_tokens=api_key.daily_tokens, monthly_tokens=api_key.monthly_tokens, daily_requests=api_key.daily_requests, monthly_requests=api_key.monthly_requests, ) result = schemas.APIKeyCreated.model_validate(db_key) result.plaintext_key = raw_key return result @app.patch("/api/api-keys/{api_key_id}/quota", response_model=schemas.APIKey) async def update_quota( api_key_id: int, quota: schemas.QuotaUpdate, db: Session = Depends(get_db), _ = Depends(require_admin_auth), ): db_key = db.query(APIKeyModel).filter(APIKeyModel.id == api_key_id).first() if not db_key: raise HTTPException(status_code=404, detail="API key not found") for field, value in quota.model_dump(exclude_unset=True).items(): setattr(db_key, field, value) db.commit() db.refresh(db_key) return db_key @app.put("/api/api-keys/{api_key_id}/deactivate") async def deactivate_api_key( api_key_id: int, db: Session = Depends(get_db), _ = Depends(require_admin_auth), ): db_key = db.query(APIKeyModel).filter(APIKeyModel.id == api_key_id).first() if not db_key: raise HTTPException(status_code=404, detail="API key not found") db_key.is_active = False db.commit() return {"message": "API key deactivated"} @app.get("/api/proxy-info") async def get_proxy_info(_ = Depends(require_admin_auth)): host = os.getenv("PROXY_HOST", "0.0.0.0") port = os.getenv("PROXY_PORT", "8000") display_host = "localhost" if host in ("0.0.0.0", "::") else host return {"endpoint": f"http://{display_host}:{port}"} @app.get("/api/settings", response_model=schemas.Settings) async def read_settings(db: Session = Depends(get_db), _ = Depends(require_admin_auth)): return schemas.Settings( ollama_url=crud.get_setting(db, "ollama_url", "http://localhost:11434"), default_model=crud.get_setting(db, "default_model", "llama3"), ) @app.put("/api/settings", response_model=schemas.Settings) async def update_settings( settings: schemas.Settings, db: Session = Depends(get_db), _ = Depends(require_admin_auth), ): crud.set_setting(db, "ollama_url", settings.ollama_url) crud.set_setting(db, "default_model", settings.default_model) return settings @app.get("/api/ollama-models") async def get_ollama_models(db: Session = Depends(get_db), _ = Depends(require_admin_auth)): ollama_url = crud.get_setting(db, "ollama_url", "http://localhost:11434") try: async with httpx.AsyncClient(timeout=5.0) as client: response = await client.get(f"{ollama_url}/api/tags") models = [m["name"] for m in response.json().get("models", [])] except Exception: models = [] return {"models": models}