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