diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d5f0ad..6371bea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **CI `update-k8s-manifest` job**: Fixed checkout of `k8s-cluster-state` repo by adding `ref: main` to the `actions/checkout` step, preventing a "Not Found" 404 error caused by the action's API call to determine the default branch. Also corrected the image tag format from `main-` to `sha-` to match the tags actually generated by `docker/metadata-action@v5` with `type=sha`. +- **`ProgrammingError` on `notification_configs`**: Added Alembic migration `0001` that runs `ALTER TABLE notification_configs ADD COLUMN IF NOT EXISTS` for the `name` and `apprise_url` columns introduced by the Apprise PR. SQLAlchemy's `create_all` does not ALTER existing tables, so existing deployments were missing these columns and crashing at runtime. The migration is idempotent (`IF NOT EXISTS`) so it is safe for fresh installs too. `app/main.py` lifespan now runs `alembic upgrade head` after `create_all`. +- **`/logs` page 404**: Created missing Next.js page at `src/app/logs/page.tsx`. The user-facing "Logs" sidebar link was pointing to `/logs` but no page existed. The new page lists all processing runs with expandable per-email log details and pagination. +- **`/admin/logs` page 404**: Created missing Next.js page at `src/app/admin/logs/page.tsx`. The admin "Activity Logs" sidebar link was pointing to `/admin/logs` but no page existed. The new page shows all processing runs across all users with status filtering and pagination. - **ESLint parse error in `DashboardLayout.tsx`**: Missing comma after `Bell` in the `lucide-react` named import caused a TypeScript parse error (`',' expected` at line 19). Added the missing comma. - **Black formatting**: `backend/app/api/v1/endpoints/admin.py` was not formatted correctly; reformatted to pass `black --check`. - **`/processing-runs` endpoint 404s**: Routes in `logs.py` had a redundant `/processing-runs` path segment (the router was already mounted at `/processing-runs` in `api.py`). All three user-facing log endpoints now return correct results: diff --git a/backend/app/main.py b/backend/app/main.py index 5dcfbf8..7c689b0 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -2,6 +2,8 @@ Main FastAPI application. """ +import asyncio +import os import re import time from contextlib import asynccontextmanager @@ -10,12 +12,20 @@ from fastapi import FastAPI, Request, Response from fastapi.middleware.cors import CORSMiddleware from prometheus_client import generate_latest, CONTENT_TYPE_LATEST import logging +from alembic.config import Config as AlembicConfig +from alembic import command as alembic_command from app.core.config import settings from app.core.middleware import SecurityHeadersMiddleware, CSRFProtectionMiddleware from app.core.metrics import HTTP_REQUESTS_TOTAL, HTTP_REQUEST_DURATION_SECONDS from app.api.v1.api import api_router +# Absolute path to alembic.ini – one level above this file's directory +# (backend/app/main.py → backend/alembic.ini) +_ALEMBIC_INI = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "alembic.ini") +) + # Configure logging logging.basicConfig( level=getattr(logging, settings.LOG_LEVEL.upper()), @@ -48,6 +58,25 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: exc_info=True, ) + # Apply schema migrations for columns added to existing tables. + # Alembic's ADD COLUMN IF NOT EXISTS migrations are idempotent – safe for + # both fresh installs (create_all already added the columns) and existing + # deployments (where the columns may be absent). + try: + + def _run_alembic_upgrade() -> None: + alembic_cfg = AlembicConfig(_ALEMBIC_INI) + alembic_command.upgrade(alembic_cfg, "head") + + await asyncio.to_thread(_run_alembic_upgrade) + logger.info("Database schema migrations applied") + except Exception as exc: + logger.error( + "Could not apply schema migrations: %s — some columns may be missing", + exc, + exc_info=True, + ) + # Seed default database-backed settings (no-op if they already exist) try: from app.core.database import async_session_maker