db: tenants-db schema — per-brand tables + Phase 1 seed

This commit is contained in:
2026-05-06 15:41:37 +00:00
parent 0e29b20d52
commit a8bc9d6d08
+182
View File
@@ -0,0 +1,182 @@
-- manar-tenants-db: Per-brand schema template
-- Run once per new brand: replace BRAND_SLUG with actual slug
-- This file seeds both brand_alsalama and brand_asktam schemas
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ── Brand Schema Template (repeat per brand) ─────────────
-- Usage: SET search_path TO brand_alsalama;
DO $$ BEGIN
CREATE SCHEMA IF NOT EXISTS brand_alsalama;
CREATE SCHEMA IF NOT EXISTS brand_asktam;
END $$;
-- ─────────────────────────────────────────────────────────
-- BRAND SCHEMA FUNCTION: creates all tables in a given schema
-- ─────────────────────────────────────────────────────────
CREATE OR REPLACE FUNCTION create_brand_schema(schema_name TEXT) RETURNS void AS $$
BEGIN
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.ad_accounts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
platform_slug TEXT NOT NULL,
external_id TEXT NOT NULL,
name TEXT,
currency TEXT DEFAULT ''SAR'',
is_active BOOLEAN NOT NULL DEFAULT true,
last_synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(platform_slug, external_id)
)', schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.ad_credentials (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ad_account_id UUID NOT NULL REFERENCES %I.ad_accounts(id) ON DELETE CASCADE,
platform_slug TEXT NOT NULL,
access_token TEXT,
refresh_token TEXT,
token_expires_at TIMESTAMPTZ,
customer_id TEXT,
login_customer_id TEXT,
extra_config JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', schema_name, schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.policies (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
scope TEXT NOT NULL DEFAULT ''account'',
rules JSONB NOT NULL DEFAULT ''[]'',
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.recommendations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ad_account_id UUID REFERENCES %I.ad_accounts(id),
type TEXT NOT NULL,
priority SMALLINT NOT NULL DEFAULT 2 CHECK (priority BETWEEN 1 AND 5),
title TEXT NOT NULL,
description TEXT,
payload JSONB,
estimated_impact JSONB,
status TEXT NOT NULL DEFAULT ''pending'' CHECK (status IN (''pending'',''approved'',''rejected'',''executed'',''vetoed'')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ
)', schema_name, schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.approvals (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
recommendation_id UUID NOT NULL REFERENCES %I.recommendations(id),
action TEXT NOT NULL CHECK (action IN (''approve'',''reject'',''veto'')),
actor_email TEXT,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', schema_name, schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.audit_log (
id BIGSERIAL PRIMARY KEY,
action TEXT NOT NULL,
entity_type TEXT,
entity_id TEXT,
actor_email TEXT,
payload JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.brand_context (
id SMALLSERIAL PRIMARY KEY,
key TEXT UNIQUE NOT NULL,
value TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.kpi_targets (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ad_account_id UUID REFERENCES %I.ad_accounts(id),
metric TEXT NOT NULL,
target_value NUMERIC NOT NULL,
period TEXT NOT NULL DEFAULT ''monthly'',
effective_from DATE NOT NULL,
effective_to DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', schema_name, schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.alert_rules (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
metric TEXT NOT NULL,
condition TEXT NOT NULL,
threshold NUMERIC NOT NULL,
severity TEXT NOT NULL DEFAULT ''warning'' CHECK (severity IN (''info'',''warning'',''critical'')),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.alert_history (
id BIGSERIAL PRIMARY KEY,
alert_rule_id UUID REFERENCES %I.alert_rules(id),
triggered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
metric_value NUMERIC,
resolved_at TIMESTAMPTZ,
payload JSONB
)', schema_name, schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.search_term_decisions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
search_term TEXT NOT NULL,
ad_account_id UUID REFERENCES %I.ad_accounts(id),
decision TEXT NOT NULL CHECK (decision IN (''add_keyword'',''add_negative'',''ignore'')),
match_type TEXT,
applied_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)', schema_name, schema_name);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.ai_memory (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
scope TEXT NOT NULL DEFAULT ''account'',
key TEXT NOT NULL,
value JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(scope, key)
)', schema_name);
END;
$$ LANGUAGE plpgsql;
-- ── Create schemas for Phase 1 brands ────────────────────
SELECT create_brand_schema('brand_alsalama');
SELECT create_brand_schema('brand_asktam');
-- ── Seed Al Salama ad account ─────────────────────────────
INSERT INTO brand_alsalama.ad_accounts (platform_slug, external_id, name, currency)
VALUES ('google_ads', '5467427558', 'Al Salama Medical - Google Ads', 'SAR');
INSERT INTO brand_alsalama.brand_context (key, value) VALUES
('brand_name', 'Al Salama Medical'),
('brand_slug', 'alsalama'),
('primary_market', 'Saudi Arabia'),
('trust_level', '0');
-- ── Seed Ask Tam brand context ────────────────────────────
INSERT INTO brand_asktam.brand_context (key, value) VALUES
('brand_name', 'Ask Tam'),
('brand_slug', 'asktam'),
('primary_market', 'Saudi Arabia'),
('trust_level', '0');