Files
MediaManager/media_manager/main.py
2026-01-01 21:05:20 +01:00

178 lines
5.5 KiB
Python

from media_manager.logging import setup_logging, LOGGING_CONFIG
from media_manager.scheduler import setup_scheduler
from media_manager.filesystem_checks import run_filesystem_checks
from media_manager.config import MediaManagerConfig
import uvicorn
import os
from fastapi import FastAPI, APIRouter
from fastapi.middleware.cors import CORSMiddleware
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from fastapi.staticfiles import StaticFiles
from starlette.responses import RedirectResponse, FileResponse, Response
from media_manager.auth.users import (
bearer_auth_backend,
fastapi_users,
cookie_auth_backend,
)
from media_manager.auth.router import (
users_router as custom_users_router,
auth_metadata_router,
get_openid_router,
)
from media_manager.auth.schemas import UserCreate, UserRead, UserUpdate
from media_manager.exceptions import (
NotFoundError,
not_found_error_exception_handler,
MediaAlreadyExists,
media_already_exists_exception_handler,
InvalidConfigError,
invalid_config_error_exception_handler,
sqlalchemy_integrity_error_handler,
ConflictError,
conflict_error_handler,
)
from sqlalchemy.exc import IntegrityError
from psycopg.errors import UniqueViolation
import media_manager.torrent.router as torrent_router
import media_manager.movies.router as movies_router
import media_manager.tv.router as tv_router
from media_manager.notification.router import router as notification_router
import logging
setup_logging()
config = MediaManagerConfig()
log = logging.getLogger(__name__)
if config.misc.development:
log.warning("Development Mode activated!")
scheduler = setup_scheduler(config, log)
run_filesystem_checks(config, log)
BASE_PATH = os.getenv("BASE_PATH", "")
FRONTEND_FILES_DIR = os.getenv("FRONTEND_FILES_DIR")
DISABLE_FRONTEND_MOUNT = os.getenv("DISABLE_FRONTEND_MOUNT", "").lower() in [
"true",
"1",
"yes",
]
app = FastAPI(root_path=BASE_PATH)
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
origins = config.misc.cors_urls
log.info(f"CORS URLs activated for following origins: {origins}")
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "PUT", "POST", "DELETE", "PATCH", "HEAD", "OPTIONS"],
)
api_app = APIRouter(prefix="/api/v1")
@api_app.get("/health")
async def hello_world() -> dict:
return {"message": "Hello World!", "version": os.getenv("PUBLIC_VERSION")}
api_app.include_router(
fastapi_users.get_auth_router(bearer_auth_backend),
prefix="/auth/jwt",
tags=["auth"],
)
api_app.include_router(
fastapi_users.get_auth_router(cookie_auth_backend),
prefix="/auth/cookie",
tags=["auth"],
)
api_app.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix="/auth",
tags=["auth"],
)
api_app.include_router(
fastapi_users.get_reset_password_router(), prefix="/auth", tags=["auth"]
)
api_app.include_router(
fastapi_users.get_verify_router(UserRead), prefix="/auth", tags=["auth"]
)
api_app.include_router(custom_users_router, tags=["users"])
api_app.include_router(
fastapi_users.get_users_router(UserRead, UserUpdate),
prefix="/users",
tags=["users"],
)
api_app.include_router(auth_metadata_router, tags=["openid"])
if get_openid_router():
api_app.include_router(get_openid_router(), tags=["openid"], prefix="/auth/oauth")
api_app.include_router(tv_router.router, prefix="/tv", tags=["tv"])
api_app.include_router(torrent_router.router, prefix="/torrent", tags=["torrent"])
api_app.include_router(movies_router.router, prefix="/movies", tags=["movie"])
api_app.include_router(
notification_router, prefix="/notification", tags=["notification"]
)
# serve static image files
app.mount(
"/api/v1/static/image",
StaticFiles(directory=config.misc.image_directory),
name="static-images",
)
app.include_router(api_app)
# handle static frontend files
if not DISABLE_FRONTEND_MOUNT:
app.mount(
"/web", StaticFiles(directory=FRONTEND_FILES_DIR, html=True), name="frontend"
)
log.debug(f"Mounted frontend at /web from {FRONTEND_FILES_DIR}")
else:
log.info("Frontend mounting disabled (DISABLE_FRONTEND_MOUNT is set)")
@app.get("/")
async def root():
return RedirectResponse(url="/web/")
@app.get("/dashboard")
async def dashboard():
return RedirectResponse(url="/web/")
@app.get("/login")
async def login():
return RedirectResponse(url="/web/")
# this will serve the custom 404 page for frontend routes, so SvelteKit can handle routing
@app.exception_handler(404)
async def not_found_handler(request, exc):
if not DISABLE_FRONTEND_MOUNT and any(
base_path in ["/web", "/dashboard", "/login"] for base_path in request.url.path
):
return FileResponse(f"{FRONTEND_FILES_DIR}/404.html")
return Response(content="Not Found", status_code=404)
# Register exception handlers for custom exceptions
app.add_exception_handler(NotFoundError, not_found_error_exception_handler)
app.add_exception_handler(MediaAlreadyExists, media_already_exists_exception_handler)
app.add_exception_handler(InvalidConfigError, invalid_config_error_exception_handler)
app.add_exception_handler(IntegrityError, sqlalchemy_integrity_error_handler)
app.add_exception_handler(UniqueViolation, sqlalchemy_integrity_error_handler)
app.add_exception_handler(ConflictError, conflict_error_handler)
if __name__ == "__main__":
uvicorn.run(
app,
host="127.0.0.1",
port=5049,
log_config=LOGGING_CONFIG,
proxy_headers=True,
forwarded_allow_ips="*",
)