"""External API endpoints for third-party access via X-API-Key.""" import json from datetime import datetime, timezone from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Header, Query, Request from sqlalchemy.orm import Session from app.database import get_db from app.models.db_models import ApiToken from app.services import airwallex_service from app.services.audit_log import create_card_log, create_audit_log router = APIRouter(prefix="/api/v1", tags=["external"]) def verify_api_key( x_api_key: str = Header(..., alias="X-API-Key"), db: Session = Depends(get_db), ) -> ApiToken: """Verify API key and return token record.""" token = db.query(ApiToken).filter(ApiToken.token == x_api_key, ApiToken.is_active == True).first() if not token: raise HTTPException(status_code=401, detail="Invalid or inactive API key") # Check expiry if token.expires_at and token.expires_at < datetime.now(timezone.utc): raise HTTPException(status_code=401, detail="API key has expired") # Update last used token.last_used_at = datetime.now(timezone.utc) db.commit() return token def check_permission(token: ApiToken, required: str): """Check if token has a required permission.""" permissions = json.loads(token.permissions) if token.permissions else [] if required not in permissions and "*" not in permissions: raise HTTPException(status_code=403, detail=f"Token lacks permission: {required}") @router.post("/cards/create") def external_create_card( card_data: dict, request: Request, db: Session = Depends(get_db), token: ApiToken = Depends(verify_api_key), ): """Create a new card (external API).""" check_permission(token, "create_cards") try: result = airwallex_service.create_card(db, card_data) create_card_log( db, action="create_card", status="success", operator=f"api:{token.name}", card_id=result.get("card_id", result.get("id", "")), cardholder_id=card_data.get("cardholder_id", ""), request_data=json.dumps(card_data), response_data=json.dumps(result, default=str), ) return result except Exception as e: create_card_log( db, action="create_card", status="failed", operator=f"api:{token.name}", cardholder_id=card_data.get("cardholder_id", ""), request_data=json.dumps(card_data), response_data=str(e), ) raise HTTPException(status_code=400, detail=str(e)) @router.get("/cards") def external_list_cards( page_num: int = Query(0, ge=0), page_size: int = Query(20, ge=1, le=100), status: Optional[str] = None, db: Session = Depends(get_db), token: ApiToken = Depends(verify_api_key), ): """List cards (external API).""" check_permission(token, "read_cards") return airwallex_service.list_cards(db, page_num, page_size, status=status) @router.get("/cards/{card_id}") def external_get_card( card_id: str, db: Session = Depends(get_db), token: ApiToken = Depends(verify_api_key), ): """Get card details (external API).""" check_permission(token, "read_cards") try: return airwallex_service.get_card(db, card_id) except Exception as e: raise HTTPException(status_code=404, detail=str(e)) @router.post("/cards/{card_id}/freeze") def external_freeze_card( card_id: str, request: Request, db: Session = Depends(get_db), token: ApiToken = Depends(verify_api_key), ): """Freeze a card (external API).""" check_permission(token, "create_cards") try: result = airwallex_service.update_card(db, card_id, {"status": "SUSPENDED"}) create_audit_log( db, action="freeze_card", resource_type="card", resource_id=card_id, operator=f"api:{token.name}", ip_address=request.client.host if request.client else "", ) return result except Exception as e: raise HTTPException(status_code=400, detail=str(e)) @router.get("/transactions") def external_list_transactions( page_num: int = Query(0, ge=0), page_size: int = Query(20, ge=1, le=100), card_id: Optional[str] = None, db: Session = Depends(get_db), token: ApiToken = Depends(verify_api_key), ): """List transactions (external API).""" check_permission(token, "read_transactions") return airwallex_service.list_transactions(db, page_num, page_size, card_id) @router.get("/balance") def external_get_balance( db: Session = Depends(get_db), token: ApiToken = Depends(verify_api_key), ): """Get account balance (external API).""" check_permission(token, "read_balance") balance = airwallex_service.get_balance(db) if balance is None: raise HTTPException(status_code=503, detail="Unable to fetch balance") return balance