db: tenants-db schema — per-brand tables + Phase 1 seed
This commit is contained in:
@@ -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');
|
||||
Reference in New Issue
Block a user