Files
Nirodan 9db922703b Add versioned DB migration system with automatic backup
migrations.py
- schema_migrations table tracks applied versions (version, description, applied_at)
- MIGRATIONS list is append-only; each entry is (version, description, sql)
- backup() dumps all user-data tables to a timestamped JSON file in backups/
  before any schema changes so data can be recovered if something goes wrong
- run_migrations() is idempotent: already-applied versions are skipped

Integration
- app.py calls _run_startup_migrations() at module load so every restart
  applies any pending migrations (no-op if schema is current)
- setup_routes.py calls run_migrations() after the initial setup form is
  submitted so all tables exist before the user hits the main page for the
  first time
- notes.py and admin.py: removed all per-request CREATE TABLE DDL; schema is
  now owned entirely by the migration system

Docker
- docker-compose.dev.yml: add backups-data volume so JSON backups survive
  container restarts and rebuilds
- Dockerfile: pre-create /app/backend/logs and /app/backend/backups so the
  directories exist even before volumes are mounted

Adding future schema changes
- Append a new (version, description, sql) tuple to MIGRATIONS in migrations.py
- The next restart will detect it as pending, back up first, then apply it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 10:27:11 +02:00

62 lines
2.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import time
import os
from flask import Blueprint, request, render_template, redirect, jsonify, send_from_directory
from util.db_config import load_config, save_config, test_connection, is_configured
from util.db_pool import reset_pool
from auth.setup_admin import initialize_admin_user
from util.logger import logger
setup_blueprint = Blueprint("setup", __name__, template_folder="../backend/templates", static_folder="../backend/static")
MAX_WAIT = 30
WAIT_INTERVAL = 10
def wait_for_db():
elapsed = 0
config = load_config()
while not test_connection(config) and elapsed < MAX_WAIT:
logger.info(f"[SETUP] DB nicht erreichbar warte {WAIT_INTERVAL}s...")
time.sleep(WAIT_INTERVAL)
elapsed += WAIT_INTERVAL
return elapsed < MAX_WAIT
@setup_blueprint.route('/api/status')
def status():
if not is_configured():
return jsonify({"status": "init", "db_connected": False})
elif test_connection(load_config()):
return jsonify({"status": "ready", "db_connected": True})
else:
return jsonify({"status": "error", "db_connected": False})
@setup_blueprint.route('/setup', methods=['GET', 'POST'])
def setup():
if request.method == 'POST':
db_config = {
"host": request.form['host'],
"port": int(request.form['port']),
"user": request.form['user'],
"password": request.form['password'],
"database": request.form['database']
}
save_config(db_config)
reset_pool()
if test_connection(db_config):
initialize_admin_user(db_config)
# Apply schema migrations immediately so all tables exist before
# the user lands on the main page.
try:
from util.db_pool import get_connection
from util.migrations import run_migrations
conn = get_connection()
try:
run_migrations(conn)
finally:
conn.close()
except Exception as e:
logger.warning(f"[setup] Post-setup migration error: {e}")
return redirect('/')
else:
return "Verbindung fehlgeschlagen. Bitte zurück und prüfen.", 500
return render_template('setup.html')