b1263edac6
users migrace má nové sloupce gitea_user_id, gitea_sync_status, gitea_sync_error, gitea_synced_at.
Přidal jsem gitea_provisioning.py, který přes Gitea API vytváří, aktivuje, deaktivuje a synchronizuje účty.
Role viewer Gitea účet nemá; developer a admin účet mají.
Změny role a enable/disable v administraci spouští sync do Gitea.
Admin UI v routes/users.py zobrazuje stav Gitea synchronizace a má ruční Sync Gitea.
Google login po přihlášení dorovnává Gitea stav podle portálového uživatele.
Gitea OAuth login je v portálu vypnutý; endpointy zůstaly, ale vrací hlášku, že se má použít Google.
Lokální login se zobrazuje/povolí jen při explicitním AUTH_MODE=local.
Gitea API token se čte z GITEA_ADMIN_TOKEN, případně fallback GITEA_API_TOKEN nebo GITEA_TOKEN. Podřízené Gitea username je deterministicky portal-{user_id}, portálová tabulka zůstává zdroj pravdy.
Ověření: python -m py_compile prošel pro všechny upravené Python soubory.
272 lines
10 KiB
Python
272 lines
10 KiB
Python
from app.db.database import get_connection
|
|
|
|
|
|
def _table_columns(con, table_name: str) -> set[str]:
|
|
rows = con.execute(f"PRAGMA table_info({table_name})").fetchall()
|
|
return {row["name"] for row in rows}
|
|
|
|
|
|
def ensure_user_columns(con) -> None:
|
|
con.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL UNIQUE,
|
|
display_name TEXT,
|
|
email TEXT,
|
|
password_hash TEXT NOT NULL DEFAULT '',
|
|
role TEXT NOT NULL DEFAULT 'viewer',
|
|
is_active INTEGER NOT NULL DEFAULT 1,
|
|
is_enabled INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
last_login_at TEXT,
|
|
auth_provider TEXT,
|
|
provider_subject TEXT,
|
|
avatar_url TEXT,
|
|
gitea_user_id INTEGER,
|
|
gitea_sync_status TEXT NOT NULL DEFAULT 'not_required',
|
|
gitea_sync_error TEXT,
|
|
gitea_synced_at TEXT
|
|
)
|
|
"""
|
|
)
|
|
columns = _table_columns(con, "users")
|
|
if not columns:
|
|
return
|
|
|
|
defaults = {
|
|
"username": "TEXT",
|
|
"display_name": "TEXT",
|
|
"email": "TEXT",
|
|
"password_hash": "TEXT NOT NULL DEFAULT ''",
|
|
"role": "TEXT NOT NULL DEFAULT 'viewer'",
|
|
"is_active": "INTEGER NOT NULL DEFAULT 1",
|
|
"is_enabled": "INTEGER NOT NULL DEFAULT 1",
|
|
"created_at": "TEXT",
|
|
"updated_at": "TEXT",
|
|
"last_login_at": "TEXT",
|
|
"auth_provider": "TEXT",
|
|
"provider_subject": "TEXT",
|
|
"avatar_url": "TEXT",
|
|
"gitea_user_id": "INTEGER",
|
|
"gitea_sync_status": "TEXT NOT NULL DEFAULT 'not_required'",
|
|
"gitea_sync_error": "TEXT",
|
|
"gitea_synced_at": "TEXT",
|
|
}
|
|
for column, definition in defaults.items():
|
|
if column not in columns:
|
|
con.execute(f"ALTER TABLE users ADD COLUMN {column} {definition}")
|
|
|
|
columns = _table_columns(con, "users")
|
|
if "is_active" in columns and "is_enabled" in columns:
|
|
con.execute("UPDATE users SET is_enabled = COALESCE(is_active, 1) WHERE is_enabled IS NULL")
|
|
con.execute("UPDATE users SET role = 'viewer' WHERE role IS NULL OR role = ''")
|
|
con.execute("UPDATE users SET gitea_sync_status = 'not_required' WHERE gitea_sync_status IS NULL OR gitea_sync_status = ''")
|
|
con.execute("UPDATE users SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULL OR created_at = ''")
|
|
con.execute("UPDATE users SET updated_at = COALESCE(created_at, CURRENT_TIMESTAMP) WHERE updated_at IS NULL OR updated_at = ''")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_users_provider_subject ON users(auth_provider, provider_subject)")
|
|
|
|
|
|
def run_migrations():
|
|
con = get_connection()
|
|
ensure_user_columns(con)
|
|
|
|
con.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS jobs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
type TEXT NOT NULL,
|
|
target_type TEXT NOT NULL,
|
|
target_id TEXT NOT NULL,
|
|
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
status TEXT NOT NULL DEFAULT 'queued',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
started_at TEXT,
|
|
finished_at TEXT,
|
|
created_by_user_id INTEGER,
|
|
created_by_username TEXT,
|
|
created_by_display_name TEXT,
|
|
source TEXT NOT NULL DEFAULT 'portal',
|
|
worker_id TEXT,
|
|
result_json TEXT,
|
|
error_text TEXT
|
|
)
|
|
"""
|
|
)
|
|
con.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS job_logs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
job_id INTEGER NOT NULL,
|
|
stream TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY(job_id) REFERENCES jobs(id)
|
|
)
|
|
"""
|
|
)
|
|
con.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS workers (
|
|
id TEXT PRIMARY KEY,
|
|
status TEXT NOT NULL DEFAULT 'online',
|
|
started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
last_seen_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
current_job_id INTEGER,
|
|
metadata_json TEXT
|
|
)
|
|
"""
|
|
)
|
|
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_jobs_status_created ON jobs(status, created_at)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_jobs_target_status ON jobs(target_type, target_id, status)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_job_logs_job_id ON job_logs(job_id, id)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_workers_last_seen ON workers(last_seen_at)")
|
|
|
|
try:
|
|
con.execute("ALTER TABLE jobs ADD COLUMN created_by_display_name TEXT")
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
con.execute("ALTER TABLE deployments ADD COLUMN job_id INTEGER")
|
|
except Exception:
|
|
pass
|
|
|
|
for statement in (
|
|
"ALTER TABLE apps ADD COLUMN description TEXT",
|
|
"ALTER TABLE apps ADD COLUMN owner TEXT",
|
|
"ALTER TABLE apps ADD COLUMN template TEXT",
|
|
"ALTER TABLE apps ADD COLUMN runtime TEXT",
|
|
"ALTER TABLE apps ADD COLUMN repository_url TEXT",
|
|
"ALTER TABLE apps ADD COLUMN repository_name TEXT",
|
|
"ALTER TABLE apps ADD COLUMN default_branch TEXT",
|
|
"ALTER TABLE apps ADD COLUMN domain TEXT",
|
|
"ALTER TABLE apps ADD COLUMN health_url TEXT",
|
|
"ALTER TABLE apps ADD COLUMN container_port INTEGER",
|
|
"ALTER TABLE apps ADD COLUMN is_public INTEGER NOT NULL DEFAULT 0",
|
|
"ALTER TABLE apps ADD COLUMN is_enabled INTEGER NOT NULL DEFAULT 1",
|
|
):
|
|
try:
|
|
con.execute(statement)
|
|
except Exception:
|
|
pass
|
|
|
|
con.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS app_templates (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
runtime TEXT,
|
|
language TEXT,
|
|
description TEXT,
|
|
default_port INTEGER,
|
|
default_health_path TEXT,
|
|
create_script TEXT,
|
|
deploy_script TEXT,
|
|
is_enabled INTEGER NOT NULL DEFAULT 1,
|
|
create_enabled INTEGER NOT NULL DEFAULT 0,
|
|
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,
|
|
FOREIGN KEY(app_id) REFERENCES apps(id)
|
|
)
|
|
"""
|
|
)
|
|
con.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS service_incidents (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
service_id TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
ended_at TEXT,
|
|
duration_seconds INTEGER,
|
|
title TEXT,
|
|
description TEXT
|
|
)
|
|
"""
|
|
)
|
|
for statement in (
|
|
"ALTER TABLE app_templates ADD COLUMN id TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN name TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN runtime TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN language TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN description TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN default_port INTEGER",
|
|
"ALTER TABLE app_templates ADD COLUMN default_health_path TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN create_script TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN deploy_script TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN is_enabled INTEGER NOT NULL DEFAULT 1",
|
|
"ALTER TABLE app_templates ADD COLUMN create_enabled INTEGER NOT NULL DEFAULT 0",
|
|
"ALTER TABLE app_templates ADD COLUMN created_at TEXT",
|
|
"ALTER TABLE app_templates ADD COLUMN updated_at TEXT",
|
|
"ALTER TABLE app_variables ADD COLUMN created_at TEXT",
|
|
"ALTER TABLE app_variables ADD COLUMN updated_at TEXT",
|
|
):
|
|
try:
|
|
con.execute(statement)
|
|
except Exception:
|
|
pass
|
|
try:
|
|
con.execute("UPDATE app_templates SET id = name WHERE id IS NULL OR id = ''")
|
|
except Exception:
|
|
pass
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_app_variables_app_id ON app_variables(app_id)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_service_incidents_status_started ON service_incidents(status, started_at)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_service_incidents_service_started ON service_incidents(service_id, started_at)")
|
|
con.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS alert_rules (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
event_type TEXT NOT NULL,
|
|
service_id TEXT,
|
|
script_name TEXT NOT NULL,
|
|
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 alert_events (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
rule_id INTEGER,
|
|
event_type TEXT NOT NULL,
|
|
service_id TEXT,
|
|
incident_id INTEGER,
|
|
status TEXT,
|
|
job_id INTEGER,
|
|
error_text TEXT,
|
|
payload_json TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
processed_at TEXT
|
|
)
|
|
"""
|
|
)
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_alert_rules_event_enabled ON alert_rules(event_type, is_enabled)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_alert_rules_service ON alert_rules(service_id)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_alert_events_rule_created ON alert_events(rule_id, created_at)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_alert_events_created ON alert_events(created_at)")
|
|
con.execute("CREATE INDEX IF NOT EXISTS idx_alert_events_job_id ON alert_events(job_id)")
|
|
|
|
con.commit()
|
|
con.close()
|