-- Meshkee CMS — initial schema -- Tables: users, businesses, domains -- Reusable trigger to keep updated_at in sync CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- --------------------------------------------------------------------------- -- users -- --------------------------------------------------------------------------- CREATE TABLE users ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, cell_number VARCHAR(20) NOT NULL, password_hash VARCHAR(255) NOT NULL, email VARCHAR(255), first_name VARCHAR(100), last_name VARCHAR(100), is_active BOOLEAN NOT NULL DEFAULT TRUE, cell_verified_at TIMESTAMPTZ, last_login_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT users_cell_number_unique UNIQUE (cell_number), CONSTRAINT users_cell_number_format CHECK (cell_number ~ '^\+[1-9]\d{6,14}$'), CONSTRAINT users_email_format_optional CHECK ( email IS NULL OR email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' ) ); CREATE INDEX idx_users_cell_number ON users (cell_number); CREATE INDEX idx_users_is_active ON users (is_active) WHERE is_active = TRUE; CREATE TRIGGER users_set_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION set_updated_at(); -- --------------------------------------------------------------------------- -- businesses (created by super admin — no direct user owner column) -- --------------------------------------------------------------------------- CREATE TABLE businesses ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(255) NOT NULL, slug VARCHAR(100) NOT NULL, description TEXT, settings 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(), CONSTRAINT businesses_slug_unique UNIQUE (slug), CONSTRAINT businesses_slug_format CHECK (slug ~ '^[a-z0-9]+(?:-[a-z0-9]+)*$') ); CREATE INDEX idx_businesses_is_active ON businesses (is_active) WHERE is_active = TRUE; CREATE TRIGGER businesses_set_updated_at BEFORE UPDATE ON businesses FOR EACH ROW EXECUTE FUNCTION set_updated_at(); -- --------------------------------------------------------------------------- -- domains (many per business) -- --------------------------------------------------------------------------- CREATE TABLE domains ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, business_id BIGINT NOT NULL, host VARCHAR(253) NOT NULL, is_primary BOOLEAN NOT NULL DEFAULT FALSE, is_verified BOOLEAN NOT NULL DEFAULT FALSE, verified_at TIMESTAMPTZ, ssl_enabled BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT domains_host_unique UNIQUE (host), CONSTRAINT domains_business_id_fkey FOREIGN KEY (business_id) REFERENCES businesses (id) ON DELETE CASCADE, CONSTRAINT domains_host_format CHECK ( host ~ '^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$' OR host ~ '^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$' ) ); CREATE INDEX idx_domains_business_id ON domains (business_id); CREATE INDEX idx_domains_host ON domains (host); CREATE INDEX idx_domains_business_primary ON domains (business_id) WHERE is_primary = TRUE; CREATE UNIQUE INDEX idx_domains_one_primary_per_business ON domains (business_id) WHERE is_primary = TRUE; CREATE TRIGGER domains_set_updated_at BEFORE UPDATE ON domains FOR EACH ROW EXECUTE FUNCTION set_updated_at();