90ac0b81d1
Add full V2 architecture: identity, content, studio (.NET 10) and file, render, notification, gateway (Go) services with vendored deps, plus DB migrations, event/API contracts, and an init-db script. Wire the Next.js frontend to the gateway: server-side JWT auth routes (login/register/refresh/logout/me), gateway fetch helper, and session/ cookie/jwt helpers under src/lib. Containerize the stack via docker-compose.v2.yml and per-service Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via next/font/local to avoid Google Fonts (geo-blocked). Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
150 lines
6.4 KiB
SQL
150 lines
6.4 KiB
SQL
-- =====================================================================
|
|
-- 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()
|
|
);
|