health check
This commit is contained in:
@@ -3,10 +3,6 @@ Core module for AniWorld application.
|
||||
Contains domain entities, interfaces, application services, and exceptions.
|
||||
"""
|
||||
|
||||
from . import entities
|
||||
from . import exceptions
|
||||
from . import interfaces
|
||||
from . import application
|
||||
from . import providers
|
||||
from . import entities, exceptions, interfaces, providers
|
||||
|
||||
__all__ = ['entities', 'exceptions', 'interfaces', 'application', 'providers']
|
||||
__all__ = ['entities', 'exceptions', 'interfaces', 'providers']
|
||||
|
||||
5
src/server/controllers/__init__.py
Normal file
5
src/server/controllers/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Controllers package for FastAPI application.
|
||||
|
||||
This package contains route controllers organized by functionality.
|
||||
"""
|
||||
39
src/server/controllers/error_controller.py
Normal file
39
src/server/controllers/error_controller.py
Normal 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
|
||||
}
|
||||
)
|
||||
31
src/server/controllers/health_controller.py
Normal file
31
src/server/controllers/health_controller.py
Normal 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
|
||||
}
|
||||
47
src/server/controllers/page_controller.py
Normal file
47
src/server/controllers/page_controller.py
Normal 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
97
src/server/fastapi_app.py
Normal 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"
|
||||
)
|
||||
6
src/server/utils/__init__.py
Normal file
6
src/server/utils/__init__.py
Normal 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.
|
||||
"""
|
||||
180
src/server/utils/dependencies.py
Normal file
180
src/server/utils/dependencies.py
Normal 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
|
||||
12
src/server/utils/templates.py
Normal file
12
src/server/utils/templates.py
Normal 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))
|
||||
42
src/server/web/templates/error.html
Normal file
42
src/server/web/templates/error.html
Normal 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>
|
||||
Reference in New Issue
Block a user