db: core-db schema — brands, users, memberships, audit

This commit is contained in:
2026-05-06 15:40:59 +00:00
parent 2f5db77367
commit ea52ab307a
+105
View File
@@ -0,0 +1,105 @@
-- manar-core-db: Registry layer
-- Shared across all brands. No tenant data here.
-- Extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ── Brands ───────────────────────────────────────────────
CREATE TABLE brands (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
slug TEXT UNIQUE NOT NULL, -- e.g. 'alsalama', 'asktam'
name TEXT NOT NULL,
logo_url TEXT,
timezone TEXT NOT NULL DEFAULT 'Asia/Riyadh',
trust_level SMALLINT NOT NULL DEFAULT 0 CHECK (trust_level BETWEEN 0 AND 3),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Users ────────────────────────────────────────────────
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT UNIQUE NOT NULL,
name TEXT,
hashed_password TEXT,
is_super_admin BOOLEAN NOT NULL DEFAULT false,
is_active BOOLEAN NOT NULL DEFAULT true,
last_login_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Brand Memberships ────────────────────────────────────
CREATE TABLE brand_memberships (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'viewer' CHECK (role IN ('viewer', 'analyst', 'manager', 'admin')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(brand_id, user_id)
);
-- ── Platforms ────────────────────────────────────────────
CREATE TABLE platforms (
id SMALLSERIAL PRIMARY KEY,
slug TEXT UNIQUE NOT NULL, -- 'google_ads', 'meta', 'tiktok'
name TEXT NOT NULL,
is_live BOOLEAN NOT NULL DEFAULT false
);
INSERT INTO platforms (slug, name, is_live) VALUES
('google_ads', 'Google Ads', true),
('meta', 'Meta Ads', false),
('tiktok', 'TikTok Ads', false),
('snapchat', 'Snapchat Ads', false),
('twitter', 'X Ads', false),
('linkedin', 'LinkedIn Ads', false);
-- ── Audit Log (global) ───────────────────────────────────
CREATE TABLE audit_log_global (
id BIGSERIAL PRIMARY KEY,
user_id UUID REFERENCES users(id),
brand_id UUID REFERENCES brands(id),
action TEXT NOT NULL,
entity_type TEXT,
entity_id TEXT,
payload JSONB,
ip_address INET,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_audit_global_brand ON audit_log_global(brand_id);
CREATE INDEX idx_audit_global_created ON audit_log_global(created_at DESC);
-- ── Super Admin Actions ──────────────────────────────────
CREATE TABLE super_admin_actions (
id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id),
action TEXT NOT NULL,
target_type TEXT,
target_id TEXT,
notes TEXT,
payload JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Seed: Brands ─────────────────────────────────────────
INSERT INTO brands (slug, name, timezone, trust_level) VALUES
('alsalama', 'Al Salama Medical', 'Asia/Riyadh', 0),
('asktam', 'Ask Tam', 'Asia/Riyadh', 0);
-- ── Seed: Super Admin ────────────────────────────────────
INSERT INTO users (email, name, is_super_admin) VALUES
('tam@asktam.net', 'Tam', true);
-- Updated_at trigger
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN NEW.updated_at = NOW(); RETURN NEW; END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_brands_updated BEFORE UPDATE ON brands
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_users_updated BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at();