Files
english/supabase/migrations/002_gamification.sql
2026-04-12 22:59:46 +07:00

120 lines
4.3 KiB
PL/PgSQL

-- Migration 002: Gamification — Xu system, XP, streak, leaderboard
-- Run in Supabase Dashboard → SQL Editor (after 001_user_progress.sql)
-- ============================================================
-- User gamification state (one row per user)
-- ============================================================
CREATE TABLE IF NOT EXISTS user_gamification (
user_id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
xp INT NOT NULL DEFAULT 0,
level TEXT NOT NULL DEFAULT 'beginner'
CHECK (level IN ('beginner', 'bronze', 'silver', 'gold', 'master')),
streak INT NOT NULL DEFAULT 0,
longest_streak INT NOT NULL DEFAULT 0,
last_active DATE,
xu INT NOT NULL DEFAULT 50, -- welcome bonus
freeze_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now()
);
-- ============================================================
-- Xu transaction log (immutable audit trail)
-- ============================================================
CREATE TABLE IF NOT EXISTS xu_transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
type TEXT NOT NULL
CHECK (type IN (
'earn_welcome', 'earn_daily', 'earn_streak', 'earn_ads',
'spend_freeze', 'spend_writing', 'spend_test'
)),
amount INT NOT NULL, -- positive = earn, negative = spend
balance INT NOT NULL, -- xu balance after this transaction
description TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_xu_transactions_user_date
ON xu_transactions(user_id, created_at DESC);
-- ============================================================
-- Weekly leaderboard (reset every week)
-- ============================================================
CREATE TABLE IF NOT EXISTS weekly_leaderboard (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
week_start DATE NOT NULL,
xp_earned INT NOT NULL DEFAULT 0,
rank INT,
UNIQUE (user_id, week_start)
);
CREATE INDEX IF NOT EXISTS idx_leaderboard_week_xp
ON weekly_leaderboard(week_start, xp_earned DESC);
-- ============================================================
-- Row Level Security
-- ============================================================
-- user_gamification
ALTER TABLE user_gamification ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can read own gamification"
ON user_gamification FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own gamification"
ON user_gamification FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own gamification"
ON user_gamification FOR UPDATE
USING (auth.uid() = user_id);
-- xu_transactions (immutable — no UPDATE/DELETE)
ALTER TABLE xu_transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can read own xu transactions"
ON xu_transactions FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own xu transactions"
ON xu_transactions FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- weekly_leaderboard (public read, own write)
ALTER TABLE weekly_leaderboard ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can read leaderboard"
ON weekly_leaderboard FOR SELECT
USING (true);
CREATE POLICY "Users can insert own leaderboard"
ON weekly_leaderboard FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own leaderboard"
ON weekly_leaderboard FOR UPDATE
USING (auth.uid() = user_id);
-- ============================================================
-- Trigger: auto-create gamification row on new user signup
-- ============================================================
CREATE OR REPLACE FUNCTION handle_new_user_gamification()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO user_gamification (user_id, xu)
VALUES (NEW.id, 50);
INSERT INTO xu_transactions (user_id, type, amount, balance, description)
VALUES (NEW.id, 'earn_welcome', 50, 50, 'Welcome bonus');
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created_gamification
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION handle_new_user_gamification();