diff --git a/.env.prod.example b/.env.prod.example index b3c0305..768cab9 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -8,7 +8,7 @@ # RELEASE VERSION # ===================================================== # Tag fra Gitea (f.eks. v1.0.0, v1.2.3) -RELEASE_VERSION=v2.0.2 +RELEASE_VERSION=v2.0.3 # ===================================================== # GITEA AUTHENTICATION diff --git a/RELEASE_NOTES_v2.0.3.md b/RELEASE_NOTES_v2.0.3.md new file mode 100644 index 0000000..16f96f1 --- /dev/null +++ b/RELEASE_NOTES_v2.0.3.md @@ -0,0 +1,8 @@ +# Release Notes v2.0.3 + +## Changes +- Allow executing SQL migration files directly from `/settings/migrations`, including user feedback on success/failure. +- Pipe the migration SQL files into the Postgres container so the execution works across Docker and Podman. + +--- +Release Date: 28. januar 2026 \ No newline at end of file diff --git a/VERSION b/VERSION index f93ea0c..6acdb44 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.2 \ No newline at end of file +2.0.3 \ No newline at end of file diff --git a/app/settings/backend/views.py b/app/settings/backend/views.py index c875255..8d4458d 100644 --- a/app/settings/backend/views.py +++ b/app/settings/backend/views.py @@ -7,6 +7,8 @@ from pathlib import Path from fastapi import APIRouter, Request, HTTPException from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates +from pydantic import BaseModel +import shlex import subprocess from app.core.config import settings @@ -49,11 +51,15 @@ async def migrations_page(request: Request): }) +class MigrationExecution(BaseModel): + file_name: str + + @router.post("/settings/migrations/execute", tags=["Frontend"]) -def execute_migration(file_name: str): +def execute_migration(payload: MigrationExecution): """Execute a migration SQL file""" migrations_dir = Path(__file__).resolve().parents[3] / "migrations" - migration_file = migrations_dir / file_name + migration_file = migrations_dir / payload.file_name if not migration_file.exists(): raise HTTPException(status_code=404, detail="Migration file not found") @@ -62,7 +68,11 @@ def execute_migration(file_name: str): container_runtime = "podman" if settings.CONTAINER_RUNTIME == "podman" else "docker" # Execute the migration file - command = f"{container_runtime} exec bmc-hub-postgres psql -U {settings.POSTGRES_USER} -d {settings.POSTGRES_DB} -f {migration_file}" + command = ( + f"cat {shlex.quote(str(migration_file))} | " + f"{container_runtime} exec -i bmc-hub-postgres " + f"psql -U {settings.POSTGRES_USER} -d {settings.POSTGRES_DB}" + ) result = subprocess.run(command, shell=True, capture_output=True, text=True) if result.returncode != 0: diff --git a/app/settings/frontend/migrations.html b/app/settings/frontend/migrations.html index 13ecc3a..6fb51e0 100644 --- a/app/settings/frontend/migrations.html +++ b/app/settings/frontend/migrations.html @@ -67,10 +67,13 @@ {{ migration.size_kb }} KB {{ migration.modified }} - + + {% endfor %} @@ -103,6 +106,7 @@ Kopiér alt + @@ -148,5 +152,34 @@ if (!currentAltCommand) return; await navigator.clipboard.writeText(currentAltCommand); } + + async function runMigration(migrationName, button) { + const feedback = document.getElementById('migrationFeedback'); + const url = '/settings/migrations/execute'; + + button.disabled = true; + feedback.className = 'alert alert-info mt-3'; + feedback.textContent = 'Kører migration...'; + feedback.classList.remove('d-none'); + + try { + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ file_name: migrationName }) + }); + + const data = await response.json(); + if (!response.ok) throw new Error(data.detail || data.message); + + feedback.className = 'alert alert-success mt-3'; + feedback.innerHTML = `Migration kørt
${data.output}
`; + } catch (error) { + feedback.className = 'alert alert-danger mt-3'; + feedback.innerHTML = `Fejl
${error.message}`; + } finally { + button.disabled = false; + } + } {% endblock %}