-- ===================================================================== -- IDENTITY SCHEMA — Part 4: Gamification (simplified) -- ===================================================================== -- Quests, gifts, loyalty — leaner than V1 but still functional. -- ===================================================================== SET search_path TO identity, public; CREATE TYPE quest_type AS ENUM ('OneTime','Daily','Weekly','Onboarding','Milestone'); CREATE TYPE prize_type AS ENUM ('Balance','RenderSeconds','LoyaltyPoints','StorageGB','Plan','Discount'); CREATE TYPE gift_type AS ENUM ('Bonus','Referral','Compensation','Promotion','Achievement'); -- --------------------------------------------------------------------- -- quests -- --------------------------------------------------------------------- CREATE TABLE quests ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, -- NULL = global title TEXT NOT NULL, challenge TEXT, why TEXT, hint TEXT, aphorism TEXT, icon TEXT, quest_type quest_type NOT NULL, target_event TEXT NOT NULL, -- 'user.registered','project.created',... target_count INT NOT NULL DEFAULT 1, -- how many times event must fire metadata JSONB NOT NULL DEFAULT '{}'::jsonb, prize_type prize_type NOT NULL, prize_amount BIGINT NOT NULL, -- minor units or seconds/points level_limit INT, -- minimum loyalty level start_url TEXT, post_action_name TEXT, order_value INT NOT NULL DEFAULT 0, starts_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_quests_active ON quests(quest_type, is_active); CREATE TRIGGER tg_quests_updated_at BEFORE UPDATE ON quests FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); -- --------------------------------------------------------------------- -- user_quest_progress — incremental tracking -- --------------------------------------------------------------------- CREATE TABLE user_quest_progress ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, quest_id UUID NOT NULL REFERENCES quests(id) ON DELETE CASCADE, current_count INT NOT NULL DEFAULT 0, text_value TEXT, -- for input-based quests is_completed BOOLEAN NOT NULL DEFAULT FALSE, completed_at TIMESTAMPTZ, prize_claimed BOOLEAN NOT NULL DEFAULT FALSE, prize_claimed_at TIMESTAMPTZ, period_start DATE, -- for Daily/Weekly resets created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (user_id, quest_id, period_start) ); CREATE INDEX idx_quest_prog_user_open ON user_quest_progress(user_id) WHERE is_completed = FALSE; CREATE TRIGGER tg_quest_prog_updated_at BEFORE UPDATE ON user_quest_progress FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); -- --------------------------------------------------------------------- -- gifts — admin-issued bonuses -- --------------------------------------------------------------------- CREATE TABLE gifts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, name TEXT NOT NULL, description TEXT, icon TEXT, gift_type gift_type NOT NULL, prize_type prize_type NOT NULL, value BIGINT NOT NULL, unit TEXT, -- 'seconds','IRR','points',... assigned_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- --------------------------------------------------------------------- -- earned_gifts — issued to user (consumed via used_gifts) -- --------------------------------------------------------------------- CREATE TABLE earned_gifts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, gift_id UUID NOT NULL REFERENCES gifts(id) ON DELETE RESTRICT, notification_id UUID, -- FK added later source TEXT, -- 'quest','admin','referral','plan' source_ref UUID, -- e.g. quest_id earned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ, is_used BOOLEAN NOT NULL DEFAULT FALSE, used_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_earned_gifts_user ON earned_gifts(user_id) WHERE is_used = FALSE; -- --------------------------------------------------------------------- -- used_gifts — claim log -- --------------------------------------------------------------------- CREATE TABLE used_gifts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, earned_gift_id UUID NOT NULL REFERENCES earned_gifts(id) ON DELETE RESTRICT, used_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_used_gifts_user ON used_gifts(user_id, used_at DESC); -- --------------------------------------------------------------------- -- avatars — preset avatar library -- --------------------------------------------------------------------- CREATE TABLE avatars ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code TEXT UNIQUE NOT NULL, url TEXT NOT NULL, description TEXT, sort INT NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() );