78 lines
4.4 KiB
SQL
78 lines
4.4 KiB
SQL
-- Migration 011: User-level referral codes + personal circle + points system
|
||
-- Extends the existing tenant-level referral system (006) to support
|
||
-- individual user-to-user referrals and a unified points economy.
|
||
--
|
||
-- Design:
|
||
-- • Each user gets their own referral code (vs. per-tenant in 006)
|
||
-- • Referral relationships track user→user (personal circle, max 2 levels)
|
||
-- • Points replace raw USD credits as the reward currency (flexible redemption)
|
||
-- • Points ledger is append-only; balance is a denormalized cache
|
||
|
||
-- ── 1. User referral codes ──────────────────────────────────────────────────
|
||
-- One row per user. Created automatically on first registration.
|
||
CREATE TABLE IF NOT EXISTS public.user_referral_codes (
|
||
user_id UUID PRIMARY KEY,
|
||
code VARCHAR(20) NOT NULL UNIQUE,
|
||
click_count INT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
-- ── 2. User referral relationships (personal circle) ────────────────────────
|
||
-- Tracks who invited whom at the user level.
|
||
-- Each user can be referred at most once (UNIQUE referred_user_id).
|
||
-- level: 1 = direct invite, 2 = indirect (friend of friend).
|
||
-- status: PENDING → ACTIVE (first engagement) → REWARDED (first payment done)
|
||
-- EXPIRED (never activated after 90 days)
|
||
CREATE TABLE IF NOT EXISTS public.user_referral_relationships (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
referrer_user_id UUID NOT NULL,
|
||
referred_user_id UUID NOT NULL UNIQUE,
|
||
referral_code VARCHAR(20) NOT NULL,
|
||
level SMALLINT NOT NULL DEFAULT 1 CHECK (level IN (1, 2)),
|
||
status VARCHAR(20) NOT NULL DEFAULT 'PENDING'
|
||
CHECK (status IN ('PENDING','ACTIVE','REWARDED','EXPIRED')),
|
||
activated_at TIMESTAMPTZ,
|
||
rewarded_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_urr_referrer ON public.user_referral_relationships(referrer_user_id);
|
||
CREATE INDEX IF NOT EXISTS idx_urr_status ON public.user_referral_relationships(status);
|
||
CREATE INDEX IF NOT EXISTS idx_urr_created ON public.user_referral_relationships(created_at);
|
||
|
||
-- ── 3. Points ledger (append-only) ──────────────────────────────────────────
|
||
-- Every point event is a row. delta > 0 = earned, delta < 0 = spent.
|
||
-- type values:
|
||
-- REFERRAL_FIRST_PAYMENT – first subscription payment by referred user
|
||
-- REFERRAL_RECURRING – recurring monthly payment reward (≤12 months)
|
||
-- REFERRAL_L2 – level-2 indirect reward (≤6 months)
|
||
-- REFERRAL_WELCOME – welcome bonus awarded to newly referred user
|
||
-- REDEMPTION_QUOTA – redeemed for extra usage quota
|
||
-- REDEMPTION_UNLOCK – redeemed to unlock a premium agent
|
||
-- ADMIN_GRANT – platform admin manual grant/deduction
|
||
-- EXPIRY – points expiry deduction
|
||
CREATE TABLE IF NOT EXISTS public.user_point_transactions (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id UUID NOT NULL,
|
||
delta INT NOT NULL,
|
||
type VARCHAR(40) NOT NULL,
|
||
ref_id UUID, -- FK to user_referral_relationships.id or redemption id
|
||
note TEXT,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_upt_user_id ON public.user_point_transactions(user_id);
|
||
CREATE INDEX IF NOT EXISTS idx_upt_created ON public.user_point_transactions(created_at);
|
||
CREATE INDEX IF NOT EXISTS idx_upt_ref_id ON public.user_point_transactions(ref_id);
|
||
|
||
-- ── 4. Points balance (denormalized cache) ───────────────────────────────────
|
||
-- Maintained atomically alongside user_point_transactions.
|
||
-- balance = total_earned + total_spent (total_spent is always ≤ 0, so balance = earned - |spent|)
|
||
CREATE TABLE IF NOT EXISTS public.user_point_balances (
|
||
user_id UUID PRIMARY KEY,
|
||
balance INT NOT NULL DEFAULT 0,
|
||
total_earned INT NOT NULL DEFAULT 0,
|
||
total_spent INT NOT NULL DEFAULT 0,
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|