Yozgat::DB::Migrations
Applies versioned SQL migrations tracked in _yozgat_migrations.
SQL is embedded at compile time so release binaries stay self-contained.
Constants
ALL = [Entry.new(1, "001_users", "CREATE TABLE users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n email TEXT NOT NULL UNIQUE,\n password_hash TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'admin',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n"), Entry.new(2, "002_refresh_tokens", "CREATE TABLE refresh_tokens (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n token_hash TEXT NOT NULL UNIQUE,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n revoked_at TEXT,\n replaced_by INTEGER REFERENCES refresh_tokens(id),\n user_agent TEXT\n);\n\nCREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);\n\nCREATE INDEX idx_refresh_tokens_active_hash ON refresh_tokens(token_hash) WHERE revoked_at IS NULL;\n"), Entry.new(3, "003_projects", "CREATE TABLE projects (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL,\n repo_url TEXT NOT NULL,\n auth_username TEXT,\n auth_token TEXT,\n webhook_secret TEXT NOT NULL,\n project_type TEXT NOT NULL CHECK (project_type IN ('static', 'dockerfile', 'dockercompose')),\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX idx_projects_repo_url ON projects(repo_url);\n"), Entry.new(4, "004_projects_status", "ALTER TABLE projects ADD COLUMN status TEXT NOT NULL DEFAULT 'not_initialized'\n CHECK (status IN (\n 'not_initialized',\n 'starting',\n 'running',\n 'stopping',\n 'stopped',\n 'deploying',\n 'failed',\n 'exited',\n 'removing',\n 'unknown'\n ));\n"), Entry.new(5, "005_environments", "CREATE TABLE environments (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n slug TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE (project_id, slug)\n);\n\nCREATE INDEX idx_environments_project_id ON environments(project_id);\n"), Entry.new(6, "006_deployments", "CREATE TABLE deployments (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n environment_id INTEGER NOT NULL REFERENCES environments(id) ON DELETE CASCADE,\n commit_hash TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN (\n 'pending',\n 'cloning',\n 'building',\n 'starting',\n 'running',\n 'failed',\n 'stopped'\n )),\n image_tag TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX idx_deployments_project_id ON deployments(project_id);\nCREATE INDEX idx_deployments_environment_id ON deployments(environment_id);\nCREATE INDEX idx_deployments_env_created_at ON deployments(environment_id, created_at);\n"), Entry.new(7, "007_domains", "CREATE TABLE domains (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n environment_id INTEGER NOT NULL REFERENCES environments(id) ON DELETE CASCADE,\n domain_name TEXT NOT NULL,\n service_name TEXT,\n port INTEGER NOT NULL DEFAULT 80,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE (project_id, environment_id, domain_name)\n);\n\nCREATE INDEX idx_domains_project_env ON domains(project_id, environment_id);\nCREATE INDEX idx_domains_domain_name ON domains(domain_name);\n"), Entry.new(8, "008_deployment_slug", "ALTER TABLE deployments ADD COLUMN deployment_slug TEXT;\n\nCREATE UNIQUE INDEX idx_deployments_slug ON deployments(project_id, environment_id, deployment_slug);\n"), Entry.new(9, "009_assigned_port", "ALTER TABLE deployments ADD COLUMN assigned_port INTEGER;\n\nCREATE INDEX idx_deployments_assigned_port ON deployments(assigned_port);\n"), Entry.new(10, "010_backfill_default_env", "INSERT INTO environments (project_id, name, slug)\nSELECT p.id, 'Production', 'prod'\nFROM projects p\nWHERE NOT EXISTS (\n SELECT 1 FROM environments e WHERE e.project_id = p.id\n);\n"), Entry.new(11, "011_environment_host_port", "ALTER TABLE environments ADD COLUMN host_port INTEGER;\n\nUPDATE environments\nSET host_port = (\n SELECT d.assigned_port\n FROM deployments d\n WHERE d.environment_id = environments.id\n AND d.assigned_port IS NOT NULL\n ORDER BY d.id DESC\n LIMIT 1\n);\n\nCREATE INDEX idx_environments_host_port ON environments(host_port);\n"), Entry.new(12, "012_environment_variables", "CREATE TABLE environment_variables (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n environment_id INTEGER NOT NULL REFERENCES environments(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE (project_id, environment_id, key)\n);\n\nCREATE INDEX idx_env_vars_project_env ON environment_variables(project_id, environment_id);\n"), Entry.new(13, "013_environment_pending_port", "ALTER TABLE environments ADD COLUMN pending_port INTEGER;\n\nCREATE INDEX idx_environments_pending_port ON environments(pending_port);\n")] of Entry
Class methods
bootstrap_legacy!(db : ::DB::Database)
Pre-migration Crystal installs used PRAGMA user_version = 1 with only
users. Mark 001 as applied without re-running CREATE TABLE.
ensure_table!(db : ::DB::Database)
Sourcerun!(db : ::DB::Database)
Source