health check

This commit is contained in:
2025-10-12 23:06:29 +02:00
parent 6a695966bf
commit 2867ebae09
13 changed files with 844 additions and 273 deletions

View File

@@ -0,0 +1,5 @@
"""
Controllers package for FastAPI application.
This package contains route controllers organized by functionality.
"""

View File

@@ -0,0 +1,39 @@
"""
Error handler controller for managing application exceptions.
This module provides custom error handlers for different HTTP status codes.
"""
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
from src.server.utils.templates import templates
async def not_found_handler(request: Request, exc: HTTPException):
"""Custom 404 handler."""
if request.url.path.startswith("/api/"):
return JSONResponse(
status_code=404,
content={"detail": "API endpoint not found"}
)
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Page not found", "status_code": 404}
)
async def server_error_handler(request: Request, exc: Exception):
"""Custom 500 handler."""
if request.url.path.startswith("/api/"):
return JSONResponse(
status_code=500,
content={"detail": "Internal server error"}
)
return templates.TemplateResponse(
"error.html",
{
"request": request,
"error": "Internal server error",
"status_code": 500
}
)

View File

@@ -0,0 +1,31 @@
"""
Health check controller for monitoring and status endpoints.
This module provides health check endpoints for application monitoring.
"""
from typing import Optional
from fastapi import APIRouter
from src.core.SeriesApp import SeriesApp
router = APIRouter(prefix="/health", tags=["health"])
def get_series_app() -> Optional[SeriesApp]:
"""Get the current SeriesApp instance."""
# This will be replaced with proper dependency injection
from src.server.fastapi_app import series_app
return series_app
@router.get("")
async def health_check():
"""Health check endpoint for monitoring."""
series_app = get_series_app()
return {
"status": "healthy",
"service": "aniworld-api",
"version": "1.0.0",
"series_app_initialized": series_app is not None
}

View File

@@ -0,0 +1,47 @@
"""
Page controller for serving HTML templates.
This module provides endpoints for serving HTML pages using Jinja2 templates.
"""
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from src.server.utils.templates import templates
router = APIRouter(tags=["pages"])
@router.get("/", response_class=HTMLResponse)
async def root(request: Request):
"""Serve the main application page."""
return templates.TemplateResponse(
"index.html",
{"request": request, "title": "Aniworld Download Manager"}
)
@router.get("/setup", response_class=HTMLResponse)
async def setup_page(request: Request):
"""Serve the setup page."""
return templates.TemplateResponse(
"setup.html",
{"request": request, "title": "Setup - Aniworld"}
)
@router.get("/login", response_class=HTMLResponse)
async def login_page(request: Request):
"""Serve the login page."""
return templates.TemplateResponse(
"login.html",
{"request": request, "title": "Login - Aniworld"}
)
@router.get("/queue", response_class=HTMLResponse)
async def queue_page(request: Request):
"""Serve the download queue page."""
return templates.TemplateResponse(
"queue.html",
{"request": request, "title": "Download Queue - Aniworld"}
)

97
src/server/fastapi_app.py Normal file
View File

@@ -0,0 +1,97 @@
"""
FastAPI application for Aniworld anime download manager.
This module provides the main FastAPI application with proper CORS
configuration, middleware setup, static file serving, and Jinja2 template
integration.
"""
from pathlib import Path
from typing import Optional
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from src.config.settings import settings
# Import core functionality
from src.core.SeriesApp import SeriesApp
from src.server.controllers.error_controller import (
not_found_handler,
server_error_handler,
)
# Import controllers
from src.server.controllers.health_controller import router as health_router
from src.server.controllers.page_controller import router as page_router
# Initialize FastAPI app
app = FastAPI(
title="Aniworld Download Manager",
description="Modern web interface for Aniworld anime download management",
version="1.0.0",
docs_url="/api/docs",
redoc_url="/api/redoc"
)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Configure appropriately for production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Configure static files
STATIC_DIR = Path(__file__).parent / "web" / "static"
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
# Include routers
app.include_router(health_router)
app.include_router(page_router)
# Global variables for application state
series_app: Optional[SeriesApp] = None
@app.on_event("startup")
async def startup_event():
"""Initialize application on startup."""
global series_app
try:
# Initialize SeriesApp with configured directory
if settings.anime_directory:
series_app = SeriesApp(settings.anime_directory)
print("FastAPI application started successfully")
except Exception as e:
print(f"Error during startup: {e}")
@app.on_event("shutdown")
async def shutdown_event():
"""Cleanup on application shutdown."""
print("FastAPI application shutting down")
@app.exception_handler(404)
async def handle_not_found(request: Request, exc: HTTPException):
"""Custom 404 handler."""
return await not_found_handler(request, exc)
@app.exception_handler(500)
async def handle_server_error(request: Request, exc: Exception):
"""Custom 500 handler."""
return await server_error_handler(request, exc)
if __name__ == "__main__":
uvicorn.run(
"fastapi_app:app",
host="127.0.0.1",
port=8000,
reload=True,
log_level="info"
)

View File

@@ -0,0 +1,6 @@
"""
Utility modules for the FastAPI application.
This package contains dependency injection, security utilities, and other
helper functions for the web application.
"""

View File

@@ -0,0 +1,180 @@
"""
Dependency injection utilities for FastAPI.
This module provides dependency injection functions for the FastAPI
application, including SeriesApp instances, database sessions, and
authentication dependencies.
"""
from typing import AsyncGenerator, Optional
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession
from src.config.settings import settings
from src.core.SeriesApp import SeriesApp
# Security scheme for JWT authentication
security = HTTPBearer()
# Global SeriesApp instance
_series_app: Optional[SeriesApp] = None
def get_series_app() -> SeriesApp:
"""
Dependency to get SeriesApp instance.
Returns:
SeriesApp: The main application instance for anime management
Raises:
HTTPException: If SeriesApp is not initialized or anime directory
is not configured
"""
global _series_app
if not settings.anime_directory:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Anime directory not configured. Please complete setup."
)
if _series_app is None:
try:
_series_app = SeriesApp(settings.anime_directory)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to initialize SeriesApp: {str(e)}"
)
return _series_app
def reset_series_app() -> None:
"""Reset the global SeriesApp instance (for testing or config changes)."""
global _series_app
_series_app = None
async def get_database_session() -> AsyncGenerator[AsyncSession, None]:
"""
Dependency to get database session.
Yields:
AsyncSession: Database session for async operations
"""
# TODO: Implement database session management
# This is a placeholder for future database implementation
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Database functionality not yet implemented"
)
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
"""
Dependency to get current authenticated user.
Args:
credentials: JWT token from Authorization header
Returns:
dict: User information
Raises:
HTTPException: If token is invalid or user is not authenticated
"""
# TODO: Implement JWT token validation
# This is a placeholder for authentication implementation
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Authentication functionality not yet implemented"
)
def require_auth(
current_user: dict = Depends(get_current_user)
) -> dict:
"""
Dependency that requires authentication.
Args:
current_user: Current authenticated user from get_current_user
Returns:
dict: User information
"""
return current_user
def optional_auth(
credentials: Optional[HTTPAuthorizationCredentials] = Depends(
HTTPBearer(auto_error=False)
)
) -> Optional[dict]:
"""
Dependency for optional authentication.
Args:
credentials: Optional JWT token from Authorization header
Returns:
Optional[dict]: User information if authenticated, None otherwise
"""
if credentials is None:
return None
try:
return get_current_user(credentials)
except HTTPException:
return None
class CommonQueryParams:
"""Common query parameters for API endpoints."""
def __init__(self, skip: int = 0, limit: int = 100):
self.skip = skip
self.limit = limit
def common_parameters(
skip: int = 0,
limit: int = 100
) -> CommonQueryParams:
"""
Dependency for common query parameters.
Args:
skip: Number of items to skip (for pagination)
limit: Maximum number of items to return
Returns:
CommonQueryParams: Common query parameters
"""
return CommonQueryParams(skip=skip, limit=limit)
# Dependency for rate limiting (placeholder)
async def rate_limit_dependency():
"""
Dependency for rate limiting API requests.
TODO: Implement rate limiting logic
"""
pass
# Dependency for request logging (placeholder)
async def log_request_dependency():
"""
Dependency for logging API requests.
TODO: Implement request logging logic
"""
pass

View File

@@ -0,0 +1,12 @@
"""
Shared templates configuration for FastAPI application.
This module provides centralized Jinja2 template configuration.
"""
from pathlib import Path
from fastapi.templating import Jinja2Templates
# Configure templates - shared across controllers
TEMPLATES_DIR = Path(__file__).parent.parent / "web" / "templates"
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error - Aniworld</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/css/styles.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">Error {{ status_code }}</h4>
</div>
<div class="card-body text-center">
<div class="mb-4">
<i class="fas fa-exclamation-triangle text-warning" style="font-size: 4rem;"></i>
</div>
<h5>{{ error }}</h5>
<p class="text-muted">
{% if status_code == 404 %}
The page you're looking for doesn't exist.
{% elif status_code == 500 %}
Something went wrong on our end. Please try again later.
{% else %}
An unexpected error occurred.
{% endif %}
</p>
<a href="/" class="btn btn-primary">Go Home</a>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://kit.fontawesome.com/your-kit-id.js" crossorigin="anonymous"></script>
</body>
</html>