diff --git a/Dockerfile b/Dockerfile index f472d8c6..9ca6cbb6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,21 @@ RUN pip install --no-cache-dir -r requirements.txt \ && find /opt/venv -type f -name "*.pyc" -delete \ && find /opt/venv -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true -# ── Stage 2: Documentation builder ────────────────────────────────────────── +# ── Stage 2: Frontend asset builder ───────────────────────────────────────── +# Compiles Tailwind CSS (a devDependency) into the minified styles.css. +# npm ci installs ALL deps (including devDependencies) so the tailwindcss CLI +# is available; using --omit=dev would cause 'tailwindcss: not found'. +FROM node:20-slim AS frontend-builder + +WORKDIR /frontend + +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci + +COPY frontend/ ./ +RUN npm run build + +# ── Stage 4: Documentation builder ────────────────────────────────────────── FROM python:3.14.3-slim AS docs-builder WORKDIR /docs @@ -43,7 +57,7 @@ COPY mkdocs.yml /docs/mkdocs.yml # Build the static documentation site RUN mkdocs build --config-file /docs/mkdocs.yml --site-dir /docs/docs_build -# ── Stage 3: Runtime image ─────────────────────────────────────────────────── +# ── Stage 5: Runtime image ─────────────────────────────────────────────────── FROM python:3.14.3-slim WORKDIR /app @@ -81,6 +95,9 @@ COPY ./RUNTIME_INFO /app/RUNTIME_INFO # Copy the pre-built MkDocs documentation site (served at /help) COPY --from=docs-builder /docs/docs_build /app/docs_build +# Copy the compiled Tailwind CSS (built in the frontend-builder stage) +COPY --from=frontend-builder /frontend/static/styles.css /app/frontend/static/styles.css + # Create necessary runtime directories in a single layer RUN mkdir -p /app/runtime_info /workdir diff --git a/app/api/dropbox.py b/app/api/dropbox.py index 3236ca83..887adf20 100644 --- a/app/api/dropbox.py +++ b/app/api/dropbox.py @@ -399,11 +399,11 @@ async def list_dropbox_folders( async def save_dropbox_settings( request: Request, refresh_token: Annotated[str, Form(...)], + _admin: AdminUser, + db: Session = Depends(get_db), app_key: Annotated[Optional[str], Form()] = None, app_secret: Annotated[Optional[str], Form()] = None, folder_path: Annotated[Optional[str], Form()] = None, - _admin: AdminUser = Depends(_require_admin), - db: Session = Depends(get_db), ): """ Save Dropbox settings to database (primary) and .env file (best-effort). diff --git a/app/api/google_drive.py b/app/api/google_drive.py index 2f6822f6..20affdc5 100644 --- a/app/api/google_drive.py +++ b/app/api/google_drive.py @@ -376,12 +376,12 @@ def format_time_remaining(time_delta): async def save_google_drive_settings( request: Request, refresh_token: Annotated[str, Form(...)], + _admin: AdminUser, + db: Session = Depends(get_db), client_id: Annotated[Optional[str], Form()] = None, client_secret: Annotated[Optional[str], Form()] = None, folder_id: Annotated[Optional[str], Form()] = None, use_oauth: Annotated[str, Form()] = "true", - _admin: AdminUser = Depends(_require_admin), - db: Session = Depends(get_db), ): """ Save Google Drive settings to the .env file (best-effort) and persist to database. diff --git a/app/api/onedrive.py b/app/api/onedrive.py index 6e722e6b..5c089f34 100644 --- a/app/api/onedrive.py +++ b/app/api/onedrive.py @@ -316,12 +316,12 @@ def format_time_remaining(time_delta): async def save_onedrive_settings( request: Request, refresh_token: Annotated[str, Form(...)], + _admin: AdminUser, + db: Session = Depends(get_db), client_id: Annotated[Optional[str], Form()] = None, client_secret: Annotated[Optional[str], Form()] = None, tenant_id: Annotated[str, Form()] = "common", folder_path: Annotated[Optional[str], Form()] = None, - _admin: AdminUser = Depends(_require_admin), - db: Session = Depends(get_db), ): """ Saves to database (primary) and .env file (best-effort). diff --git a/app/main.py b/app/main.py index 9315e03c..5a0c6816 100644 --- a/app/main.py +++ b/app/main.py @@ -297,13 +297,13 @@ async def lifespan(app: FastAPI): try: logging.info("Application shutting down") except Exception: - pass # noqa: S110 + _startup_logger.exception("Error during shutdown logging") # Send shutdown notification try: notify_shutdown() except Exception: - pass # noqa: S110 + _startup_logger.exception("Error sending shutdown notification") app = FastAPI(