diff --git a/Dockerfile b/Dockerfile index 7b0378f..5016d57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,6 @@ COPY media_manager ./media_manager COPY alembic ./alembic COPY alembic.ini . -HEALTHCHECK CMD curl -f http://localhost:8000${MISC__API_BASE_PATH}/ || exit 1 +HEALTHCHECK CMD curl -f http://localhost:8000${MISC__API_BASE_PATH}/health || exit 1 EXPOSE 8000 CMD ["/app/mediamanager-backend-startup.sh"] diff --git a/media_manager/config.py b/media_manager/config.py index 7ff42c4..0517b39 100644 --- a/media_manager/config.py +++ b/media_manager/config.py @@ -36,7 +36,7 @@ class BasicConfig(BaseSettings): movie_directory: Path = Path(__file__).parent.parent / "data" / "movies" torrent_directory: Path = Path(__file__).parent.parent / "data" / "torrents" - frontend_url: AnyHttpUrl = "http://localhost:3000/" + frontend_url: AnyHttpUrl = "http://localhost:3000/web/" cors_urls: list[str] = [] development: bool = False api_base_path: str = "/api/v1" diff --git a/media_manager/main.py b/media_manager/main.py index fd1b500..04e089a 100644 --- a/media_manager/main.py +++ b/media_manager/main.py @@ -6,6 +6,7 @@ from pathlib import Path from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from pythonjsonlogger.json import JsonFormatter +from starlette.responses import FileResponse import media_manager.database @@ -90,7 +91,7 @@ from media_manager.exceptions import ( # noqa: E402 import shutil # noqa: E402 -from fastapi import FastAPI # noqa: E402 +from fastapi import FastAPI, APIRouter # noqa: E402 from fastapi.middleware.cors import CORSMiddleware # noqa: E402 from datetime import datetime # noqa: E402 from contextlib import asynccontextmanager # noqa: E402 @@ -146,9 +147,8 @@ async def lifespan(app: FastAPI): scheduler.shutdown() -base_path = config.misc.api_base_path -log.info("Base Path for API: %s", base_path) -app = FastAPI(root_path=base_path, lifespan=lifespan) +app = FastAPI(lifespan=lifespan, root_path="") +FRONTEND_FILES_DIR = "/app/web/build" origins = config.misc.cors_urls log.info("CORS URLs activated for following origins:") @@ -163,32 +163,33 @@ app.add_middleware( allow_headers=["*"], ) +api_app = APIRouter(prefix="/api/v1") # ---------------------------- # Standard Auth Routers # ---------------------------- -app.include_router( +api_app.include_router( fastapi_users.get_auth_router(bearer_auth_backend), prefix="/auth/jwt", tags=["auth"], ) -app.include_router( +api_app.include_router( fastapi_users.get_auth_router(cookie_auth_backend), prefix="/auth/cookie", tags=["auth"], ) -app.include_router( +api_app.include_router( fastapi_users.get_register_router(UserRead, UserCreate), prefix="/auth", tags=["auth"], ) -app.include_router( +api_app.include_router( fastapi_users.get_reset_password_router(), prefix="/auth", tags=["auth"], ) -app.include_router( +api_app.include_router( fastapi_users.get_verify_router(UserRead), prefix="/auth", tags=["auth"], @@ -198,8 +199,8 @@ app.include_router( # User Management Routers # ---------------------------- -app.include_router(custom_users_router, tags=["users"]) -app.include_router( +api_app.include_router(custom_users_router, tags=["users"]) +api_app.include_router( fastapi_users.get_users_router(UserRead, UserUpdate), prefix="/users", tags=["users"], @@ -209,10 +210,10 @@ app.include_router( # OpenID Connect Routers # ---------------------------- -app.include_router(auth_metadata_router, tags=["openid"]) +api_app.include_router(auth_metadata_router, tags=["openid"]) if openid_client is not None: - app.include_router( + api_app.include_router( get_oauth_router( oauth_client=openid_client, backend=openid_cookie_auth_backend, @@ -225,11 +226,13 @@ if openid_client is not None: tags=["openid"], ) -app.include_router(tv_router.router, prefix="/tv", tags=["tv"]) -app.include_router(torrent_router.router, prefix="/torrent", tags=["torrent"]) -app.include_router(movies_router.router, prefix="/movies", tags=["movie"]) -app.include_router(notification_router, prefix="/notification", tags=["notification"]) -app.mount( +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"] +) +api_app.mount( "/static/image", StaticFiles(directory=config.misc.image_directory), name="static-images", @@ -242,8 +245,36 @@ app.mount( 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.exception_handler(404) +async def not_found_handler(request, exc): + return FileResponse(f"{FRONTEND_FILES_DIR}/404.html") +# ---------------------------- +# Hello World +# ---------------------------- + + +@api_app.get("/health") +async def hello_world() -> dict: + """ + A simple endpoint to check if the API is running. + """ + return {"message": "Hello World!", "version": os.getenv("PUBLIC_VERSION")} + + log.info("Hello World!") +# ---------------------------- +# Include Api Router +# ---------------------------- + +app.include_router(api_app) + +# ---------------------------- +# Frontend Static Files +# ---------------------------- +app.mount("/web", StaticFiles(directory=FRONTEND_FILES_DIR, html=True), name="frontend") + # ---------------------------- # Startup filesystem checks # ---------------------------- @@ -294,14 +325,6 @@ except Exception as e: log.error(f"Error creating test directory: {e}") raise - -@app.get("/") -async def hello_world() -> dict: - """ - A simple endpoint to check if the API is running. - """ - return {"message": "Hello World!", "version": os.getenv("PUBLIC_VERSION")} - - if __name__ == "__main__": + # TODO: add feature to run app with different root_path uvicorn.run(app, host="127.0.0.1", port=5049, log_config=LOGGING_CONFIG)