From ee2ee6615ff48f42802dffa1d747350127e11672 Mon Sep 17 00:00:00 2001 From: Jiri Uhlir Date: Mon, 1 Jun 2026 12:59:46 +0200 Subject: [PATCH] Add app metadata database migration --- scripts/migrate-app-metadata-db.sh | 161 +++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100755 scripts/migrate-app-metadata-db.sh diff --git a/scripts/migrate-app-metadata-db.sh b/scripts/migrate-app-metadata-db.sh new file mode 100755 index 0000000..613ef4d --- /dev/null +++ b/scripts/migrate-app-metadata-db.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +set -euo pipefail + +DB_FILE="/opt/appfactory/data/appfactory/appfactory.db" + +if [ ! -f "$DB_FILE" ]; then + echo "Missing database: $DB_FILE" + exit 1 +fi + +python3 - "$DB_FILE" <<'PY' +import sqlite3 +import sys + +db_file = sys.argv[1] +con = sqlite3.connect(db_file) + +columns = [row[1] for row in con.execute("PRAGMA table_info(apps)").fetchall()] + +def add_column(name: str, definition: str): + if name not in columns: + con.execute(f"ALTER TABLE apps ADD COLUMN {name} {definition}") + +add_column("description", "TEXT") +add_column("owner", "TEXT") +add_column("template", "TEXT") +add_column("runtime", "TEXT") +add_column("repository_url", "TEXT") +add_column("repository_name", "TEXT") +add_column("default_branch", "TEXT NOT NULL DEFAULT 'main'") +add_column("domain", "TEXT") +add_column("health_url", "TEXT") +add_column("container_port", "INTEGER") +add_column("is_public", "INTEGER NOT NULL DEFAULT 1") +add_column("is_enabled", "INTEGER NOT NULL DEFAULT 1") + +con.execute(""" +CREATE TABLE IF NOT EXISTS app_templates ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + runtime TEXT NOT NULL, + language TEXT NOT NULL, + description TEXT, + default_port INTEGER, + default_health_path TEXT NOT NULL DEFAULT '/health', + is_enabled INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +) +""") + +con.execute(""" +CREATE TABLE IF NOT EXISTS app_variables ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + app_id TEXT NOT NULL, + key TEXT NOT NULL, + value TEXT, + is_secret INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(app_id, key) +) +""") + +con.execute(""" +CREATE INDEX IF NOT EXISTS idx_app_variables_app_id +ON app_variables(app_id) +""") + +templates = [ + ( + "fastapi", + "FastAPI", + "python", + "python", + "Python FastAPI service", + 8000, + "/health", + ), + ( + "dotnet-api", + ".NET API", + "dotnet", + "csharp", + ".NET Web API service", + 8080, + "/health", + ), + ( + "dotnet-worker", + ".NET Worker", + "dotnet", + "csharp", + ".NET background worker service", + None, + "/health", + ), + ( + "nodejs-ts", + "Node.js TypeScript", + "node", + "typescript", + "Node.js service written in TypeScript", + 3000, + "/health", + ), + ( + "static-site", + "Static Site", + "static", + "html", + "Static website served by gateway", + 80, + "/", + ), +] + +con.executemany( + """ + INSERT INTO app_templates ( + id, + name, + runtime, + language, + description, + default_port, + default_health_path + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + name = excluded.name, + runtime = excluded.runtime, + language = excluded.language, + description = excluded.description, + default_port = excluded.default_port, + default_health_path = excluded.default_health_path, + updated_at = CURRENT_TIMESTAMP + """, + templates, +) + +con.execute(""" +UPDATE apps +SET + runtime = COALESCE(runtime, language), + template = COALESCE(template, CASE + WHEN language = 'python' THEN 'fastapi' + ELSE language + END), + repository_name = COALESCE(repository_name, id), + health_url = COALESCE(health_url, '/health'), + container_port = COALESCE(container_port, 8000), + is_public = COALESCE(is_public, 1), + is_enabled = COALESCE(is_enabled, 1) +""") + +con.commit() +con.close() + +print("App metadata migration completed.") +PY