CREATE TABLE IF NOT EXISTS applications ( id BIGSERIAL PRIMARY KEY, app_code VARCHAR(64) UNIQUE NOT NULL, name VARCHAR(128) NOT NULL, department VARCHAR(128) NOT NULL DEFAULT '', owner VARCHAR(64) NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS environments ( id BIGSERIAL PRIMARY KEY, env_code VARCHAR(32) UNIQUE NOT NULL, name VARCHAR(64) NOT NULL, sort_order INT NOT NULL DEFAULT 0, description TEXT NOT NULL DEFAULT '' ); CREATE TABLE IF NOT EXISTS namespaces ( id BIGSERIAL PRIMARY KEY, app_id BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE, name VARCHAR(64) NOT NULL, format VARCHAR(16) NOT NULL DEFAULT 'properties', description TEXT NOT NULL DEFAULT '', UNIQUE(app_id, name), CONSTRAINT namespaces_format_check CHECK (format IN ('properties', 'yaml', 'json', 'xml')) ); CREATE TABLE IF NOT EXISTS config_items ( id BIGSERIAL PRIMARY KEY, namespace_id BIGINT NOT NULL REFERENCES namespaces(id) ON DELETE CASCADE, env_id BIGINT NOT NULL REFERENCES environments(id) ON DELETE CASCADE, key VARCHAR(256) NOT NULL, value TEXT NOT NULL, released_value TEXT, pending_delete BOOLEAN NOT NULL DEFAULT false, comment TEXT NOT NULL DEFAULT '', updated_by VARCHAR(64) NOT NULL DEFAULT 'admin', updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(namespace_id, env_id, key) ); CREATE TABLE IF NOT EXISTS releases ( id BIGSERIAL PRIMARY KEY, namespace_id BIGINT NOT NULL REFERENCES namespaces(id) ON DELETE CASCADE, env_id BIGINT NOT NULL REFERENCES environments(id) ON DELETE CASCADE, version INT NOT NULL, snapshot JSONB NOT NULL, diff_added INT NOT NULL DEFAULT 0, diff_modified INT NOT NULL DEFAULT 0, diff_removed INT NOT NULL DEFAULT 0, comment TEXT NOT NULL DEFAULT '', operator VARCHAR(64) NOT NULL, etcd_revision BIGINT, status VARCHAR(16) NOT NULL DEFAULT 'pending', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(namespace_id, env_id, version), CONSTRAINT releases_status_check CHECK (status IN ('pending', 'applied', 'failed')) ); CREATE TABLE IF NOT EXISTS release_outbox ( id BIGSERIAL PRIMARY KEY, release_id BIGINT NOT NULL REFERENCES releases(id) ON DELETE CASCADE, etcd_key VARCHAR(512) NOT NULL, payload JSONB NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'pending', retry_count INT NOT NULL DEFAULT 0, last_error TEXT NOT NULL DEFAULT '', next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(), locked_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT outbox_status_check CHECK (status IN ('pending', 'processing', 'done', 'failed')) ); CREATE INDEX IF NOT EXISTS release_outbox_poll_idx ON release_outbox(status, next_attempt_at, id); CREATE INDEX IF NOT EXISTS releases_scope_idx ON releases(namespace_id, env_id, version DESC); CREATE INDEX IF NOT EXISTS config_items_scope_idx ON config_items(namespace_id, env_id, key); CREATE TABLE IF NOT EXISTS users ( id BIGSERIAL PRIMARY KEY, username VARCHAR(64) UNIQUE NOT NULL, password_hash VARCHAR(256) NOT NULL, display_name VARCHAR(64) NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS roles ( id BIGSERIAL PRIMARY KEY, name VARCHAR(32) UNIQUE NOT NULL ); CREATE TABLE IF NOT EXISTS user_app_roles ( user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, app_id BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE, role_id BIGINT NOT NULL REFERENCES roles(id) ON DELETE CASCADE, PRIMARY KEY (user_id, app_id) ); CREATE TABLE IF NOT EXISTS audit_logs ( id BIGSERIAL PRIMARY KEY, actor VARCHAR(64) NOT NULL, action VARCHAR(32) NOT NULL, target_type VARCHAR(32) NOT NULL, target_id BIGINT, detail JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS audit_logs_created_idx ON audit_logs(created_at DESC); CREATE TABLE IF NOT EXISTS gray_rules ( id BIGSERIAL PRIMARY KEY, namespace_id BIGINT NOT NULL REFERENCES namespaces(id) ON DELETE CASCADE, env_id BIGINT NOT NULL REFERENCES environments(id) ON DELETE CASCADE, rule_type VARCHAR(16) NOT NULL, rule_value JSONB NOT NULL, overrides JSONB NOT NULL, enabled BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); INSERT INTO environments(env_code, name, sort_order, description) VALUES ('DEV', '开发环境', 1, '开发联调使用'), ('TEST', '测试环境', 2, 'QA 测试验证'), ('STAGING', '预发环境', 3, '上线前灰度验证'), ('PROD', '生产环境', 4, '线上正式环境') ON CONFLICT (env_code) DO NOTHING; INSERT INTO roles(name) VALUES ('admin'), ('app-owner'), ('viewer') ON CONFLICT (name) DO NOTHING;