Coverage for src/alprina_cli/api/main.py: 69%
64 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
1"""
2Alprina API - Main Application
3FastAPI-based REST API for security scanning.
4"""
6from fastapi import FastAPI, HTTPException, Depends, Header, Request
7from fastapi.responses import JSONResponse
8from fastapi.middleware.cors import CORSMiddleware
9from typing import Optional
10import sys
11import os
12from pathlib import Path
14# Add parent directory to path to import existing modules
15sys.path.insert(0, str(Path(__file__).parent.parent))
17from ..security_engine import run_agent, run_local_scan, AGENTS_AVAILABLE
18from ..agent_bridge import SecurityAgentBridge
20# Initialize FastAPI app
21app = FastAPI(
22 title="Alprina API",
23 description="AI-powered security scanning and vulnerability detection",
24 version="1.0.0",
25 docs_url="/docs",
26 redoc_url="/redoc",
27)
29# CORS configuration - allow specific origins for production
30# Note: Can't use allow_origins=["*"] with allow_credentials=True
31CORS_ORIGINS = os.getenv("CORS_ORIGINS", "https://alprina.com,https://www.alprina.com,http://localhost:3000")
33# Split comma-separated origins
34origins = [origin.strip() for origin in CORS_ORIGINS.split(",")]
36# Add CORS middleware
37app.add_middleware(
38 CORSMiddleware,
39 allow_origins=origins, # Specific origins (not wildcard)
40 allow_credentials=True, # Required for Authorization headers
41 allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
42 allow_headers=["*"],
43 expose_headers=["*"],
44)
46# Import route modules
47from .routes import scan, agents, auth, scans, device_auth, polar_webhooks, billing, subscription, alerts, insights, github_webhooks, team, dashboard, badge, cron
49# Import services for startup/shutdown
50from .services.neon_service import neon_service
52# Include routers
53app.include_router(scan.router, prefix="/v1", tags=["Scanning"])
54app.include_router(agents.router, prefix="/v1", tags=["Agents"])
55app.include_router(auth.router, prefix="/v1", tags=["Authentication"])
56app.include_router(scans.router, prefix="/v1", tags=["Scan Management"])
57app.include_router(device_auth.router, prefix="/v1", tags=["Device Authorization"])
58app.include_router(polar_webhooks.router, prefix="/v1", tags=["Billing & Webhooks"])
59app.include_router(github_webhooks.router, prefix="/v1", tags=["GitHub Integration"])
60app.include_router(billing.router, tags=["Billing"])
61app.include_router(badge.router, prefix="/v1/badge", tags=["Security Badge"])
62app.include_router(subscription.router, prefix="/v1", tags=["Subscription Management"])
63app.include_router(alerts.router, prefix="/v1", tags=["Alerts & Notifications"])
64app.include_router(insights.router, prefix="/v1", tags=["Security Insights"])
65app.include_router(team.router, prefix="/v1", tags=["Team Management"])
66app.include_router(dashboard.router, prefix="/v1", tags=["Dashboard"])
67app.include_router(cron.router, prefix="/v1", tags=["Cron Jobs"])
70# Startup and shutdown events
71@app.on_event("startup")
72async def startup_event():
73 """Initialize services on startup."""
74 from loguru import logger
75 logger.info("🚀 Starting Alprina API...")
77 # Initialize Neon connection pool
78 if neon_service.is_enabled():
79 try:
80 await neon_service.get_pool()
81 logger.info("✅ Neon connection pool initialized")
82 except Exception as e:
83 logger.error(f"❌ Failed to initialize Neon connection pool: {e}")
84 else:
85 logger.warning("⚠️ Neon service not enabled (DATABASE_URL not set)")
88@app.on_event("shutdown")
89async def shutdown_event():
90 """Clean up resources on shutdown."""
91 from loguru import logger
92 logger.info("🛑 Shutting down Alprina API...")
94 # Close Neon connection pool
95 if neon_service.is_enabled():
96 await neon_service.close()
97 logger.info("✅ Neon connection pool closed")
100@app.get("/")
101async def root():
102 """API root endpoint."""
103 return {
104 "name": "Alprina API",
105 "version": "1.0.0",
106 "description": "AI-powered security scanning",
107 "docs": "/docs",
108 "security_engine": "active" if AGENTS_AVAILABLE else "fallback",
109 "endpoints": {
110 "scan_code": "POST /v1/scan/code",
111 "list_agents": "GET /v1/agents",
112 "register": "POST /v1/auth/register",
113 "login": "POST /v1/auth/login",
114 "documentation": "GET /docs"
115 }
116 }
119@app.get("/health")
120async def health_check():
121 """Health check endpoint."""
122 return {
123 "status": "healthy",
124 "security_engine": "active" if AGENTS_AVAILABLE else "fallback",
125 "version": "1.0.0"
126 }
129# Error handlers
130@app.exception_handler(HTTPException)
131async def http_exception_handler(request: Request, exc: HTTPException):
132 """Handle HTTP exceptions."""
133 return JSONResponse(
134 status_code=exc.status_code,
135 content={
136 "error": {
137 "code": exc.status_code,
138 "message": exc.detail,
139 "documentation_url": "https://docs.alprina.com/errors"
140 }
141 }
142 )
145@app.exception_handler(Exception)
146async def general_exception_handler(request: Request, exc: Exception):
147 """Handle general exceptions."""
148 return JSONResponse(
149 status_code=500,
150 content={
151 "error": {
152 "code": "server_error",
153 "message": "An internal server error occurred",
154 "documentation_url": "https://docs.alprina.com/errors/server_error"
155 }
156 }
157 )
160# Production server configuration
161if __name__ == "__main__":
162 import uvicorn
164 uvicorn.run(
165 "alprina_cli.api.main:app",
166 host="0.0.0.0",
167 port=8000,
168 timeout_keep_alive=600, # 10 minutes for long-running AI scans
169 timeout_notify=570, # Notify client at 9.5 minutes
170 reload=False, # Disable reload in production
171 )