forked from wangziqi/gongxue-base
feat: add point activities and exchange
This commit is contained in:
178
supabase/migrations/202606290032_points_activities_exchange.sql
Normal file
178
supabase/migrations/202606290032_points_activities_exchange.sql
Normal file
@@ -0,0 +1,178 @@
|
||||
create table if not exists public.point_activity_tasks (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
legacy_id text,
|
||||
code citext not null,
|
||||
title text not null,
|
||||
description text,
|
||||
task_type text not null default 'manual'
|
||||
check (task_type in (
|
||||
'daily_check_in',
|
||||
'feedback_submit',
|
||||
'feedback_resolved',
|
||||
'practice_complete',
|
||||
'vocabulary_review',
|
||||
'mock_exam_submit',
|
||||
'manual'
|
||||
)),
|
||||
reward_points integer not null default 0 check (reward_points > 0 and reward_points <= 100000),
|
||||
claim_limit_per_user integer not null default 1 check (claim_limit_per_user > 0 and claim_limit_per_user <= 100000),
|
||||
period_type text not null default 'once'
|
||||
check (period_type in ('once', 'daily', 'weekly', 'monthly', 'unlimited')),
|
||||
valid_from timestamptz,
|
||||
valid_to timestamptz,
|
||||
status text not null default 'active'
|
||||
check (status in ('active', 'disabled', 'archived')),
|
||||
sort_order integer not null default 0,
|
||||
metadata jsonb not null default '{}'::jsonb,
|
||||
created_by uuid references public.platform_users(id) on delete set null,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (tenant_id, code),
|
||||
unique (tenant_id, legacy_id)
|
||||
);
|
||||
|
||||
comment on table public.point_activity_tasks is
|
||||
'Tenant-scoped score activity tasks configured by marketing/operations teams.';
|
||||
|
||||
create table if not exists public.user_point_activity_claims (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
user_id uuid not null references public.platform_users(id) on delete cascade,
|
||||
task_id uuid not null references public.point_activity_tasks(id) on delete cascade,
|
||||
period_key text not null,
|
||||
score_event_id uuid references public.user_score_events(id) on delete set null,
|
||||
source_type text,
|
||||
source_id uuid,
|
||||
status text not null default 'claimed'
|
||||
check (status in ('claimed', 'cancelled')),
|
||||
metadata jsonb not null default '{}'::jsonb,
|
||||
claimed_at timestamptz not null default now(),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (tenant_id, user_id, task_id, period_key)
|
||||
);
|
||||
|
||||
comment on table public.user_point_activity_claims is
|
||||
'Idempotent student claim records for activity rewards.';
|
||||
|
||||
create table if not exists public.point_exchange_items (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
legacy_id text,
|
||||
code citext not null,
|
||||
title text not null,
|
||||
description text,
|
||||
cost_points integer not null check (cost_points > 0 and cost_points <= 10000000),
|
||||
item_type text not null default 'manual'
|
||||
check (item_type in ('coupon', 'manual', 'asset', 'custom')),
|
||||
coupon_id uuid references public.coupons(id) on delete set null,
|
||||
asset_id uuid references public.content_assets(id) on delete set null,
|
||||
stock_total integer check (stock_total is null or stock_total >= 0),
|
||||
stock_used integer not null default 0 check (stock_used >= 0),
|
||||
per_user_limit integer not null default 1 check (per_user_limit > 0 and per_user_limit <= 100000),
|
||||
valid_from timestamptz,
|
||||
valid_to timestamptz,
|
||||
status text not null default 'active'
|
||||
check (status in ('active', 'disabled', 'archived')),
|
||||
sort_order integer not null default 0,
|
||||
metadata jsonb not null default '{}'::jsonb,
|
||||
created_by uuid references public.platform_users(id) on delete set null,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (tenant_id, code),
|
||||
unique (tenant_id, legacy_id),
|
||||
check (stock_total is null or stock_used <= stock_total)
|
||||
);
|
||||
|
||||
comment on table public.point_exchange_items is
|
||||
'Tenant-scoped score exchange catalog. Coupon fulfillment is supported, activation-code fulfillment is intentionally kept out of the first contract.';
|
||||
|
||||
create table if not exists public.user_point_exchange_orders (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
user_id uuid not null references public.platform_users(id) on delete cascade,
|
||||
item_id uuid not null references public.point_exchange_items(id) on delete restrict,
|
||||
status text not null default 'completed'
|
||||
check (status in ('completed', 'pending_fulfillment', 'cancelled')),
|
||||
cost_points integer not null check (cost_points > 0),
|
||||
score_event_id uuid references public.user_score_events(id) on delete set null,
|
||||
coupon_redemption_id uuid references public.coupon_redemptions(id) on delete set null,
|
||||
asset_id uuid references public.content_assets(id) on delete set null,
|
||||
idempotency_key text,
|
||||
metadata jsonb not null default '{}'::jsonb,
|
||||
exchanged_at timestamptz not null default now(),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (tenant_id, user_id, idempotency_key)
|
||||
);
|
||||
|
||||
comment on table public.user_point_exchange_orders is
|
||||
'Student score exchange orders. Each completed order has a negative redeem_cost ledger event.';
|
||||
|
||||
create index if not exists idx_point_activity_tasks_tenant_status
|
||||
on public.point_activity_tasks(tenant_id, status, sort_order, created_at desc);
|
||||
|
||||
create index if not exists idx_point_activity_claims_user
|
||||
on public.user_point_activity_claims(tenant_id, user_id, claimed_at desc);
|
||||
|
||||
create index if not exists idx_point_activity_claims_task
|
||||
on public.user_point_activity_claims(tenant_id, task_id, claimed_at desc);
|
||||
|
||||
create index if not exists idx_point_exchange_items_tenant_status
|
||||
on public.point_exchange_items(tenant_id, status, sort_order, created_at desc);
|
||||
|
||||
create index if not exists idx_point_exchange_orders_user
|
||||
on public.user_point_exchange_orders(tenant_id, user_id, exchanged_at desc);
|
||||
|
||||
create index if not exists idx_point_exchange_orders_item
|
||||
on public.user_point_exchange_orders(tenant_id, item_id, exchanged_at desc);
|
||||
|
||||
alter table public.point_activity_tasks enable row level security;
|
||||
alter table public.user_point_activity_claims enable row level security;
|
||||
alter table public.point_exchange_items enable row level security;
|
||||
alter table public.user_point_exchange_orders enable row level security;
|
||||
|
||||
drop policy if exists tenant_isolation on public.point_activity_tasks;
|
||||
create policy tenant_isolation on public.point_activity_tasks
|
||||
for all
|
||||
using (tenant_id = app.current_tenant_id() or app.is_platform_admin())
|
||||
with check (tenant_id = app.current_tenant_id() or app.is_platform_admin());
|
||||
|
||||
drop policy if exists tenant_isolation on public.user_point_activity_claims;
|
||||
create policy tenant_isolation on public.user_point_activity_claims
|
||||
for all
|
||||
using (tenant_id = app.current_tenant_id() or app.is_platform_admin())
|
||||
with check (tenant_id = app.current_tenant_id() or app.is_platform_admin());
|
||||
|
||||
drop policy if exists tenant_isolation on public.point_exchange_items;
|
||||
create policy tenant_isolation on public.point_exchange_items
|
||||
for all
|
||||
using (tenant_id = app.current_tenant_id() or app.is_platform_admin())
|
||||
with check (tenant_id = app.current_tenant_id() or app.is_platform_admin());
|
||||
|
||||
drop policy if exists tenant_isolation on public.user_point_exchange_orders;
|
||||
create policy tenant_isolation on public.user_point_exchange_orders
|
||||
for all
|
||||
using (tenant_id = app.current_tenant_id() or app.is_platform_admin())
|
||||
with check (tenant_id = app.current_tenant_id() or app.is_platform_admin());
|
||||
|
||||
drop trigger if exists set_updated_at on public.point_activity_tasks;
|
||||
create trigger set_updated_at
|
||||
before update on public.point_activity_tasks
|
||||
for each row execute function app.touch_updated_at();
|
||||
|
||||
drop trigger if exists set_updated_at on public.user_point_activity_claims;
|
||||
create trigger set_updated_at
|
||||
before update on public.user_point_activity_claims
|
||||
for each row execute function app.touch_updated_at();
|
||||
|
||||
drop trigger if exists set_updated_at on public.point_exchange_items;
|
||||
create trigger set_updated_at
|
||||
before update on public.point_exchange_items
|
||||
for each row execute function app.touch_updated_at();
|
||||
|
||||
drop trigger if exists set_updated_at on public.user_point_exchange_orders;
|
||||
create trigger set_updated_at
|
||||
before update on public.user_point_exchange_orders
|
||||
for each row execute function app.touch_updated_at();
|
||||
@@ -0,0 +1,28 @@
|
||||
do $$
|
||||
begin
|
||||
if exists (
|
||||
select 1
|
||||
from pg_constraint
|
||||
where conname = 'badges_unlock_type_check'
|
||||
) then
|
||||
alter table public.badges drop constraint badges_unlock_type_check;
|
||||
end if;
|
||||
|
||||
alter table public.badges
|
||||
add constraint badges_unlock_type_check
|
||||
check (
|
||||
unlock_type is null
|
||||
or unlock_type in (
|
||||
'manual',
|
||||
'auto',
|
||||
'score',
|
||||
'check_in',
|
||||
'practice_count',
|
||||
'vocabulary_mastered',
|
||||
'mock_exam_score',
|
||||
'feedback_resolved',
|
||||
'activity_reward',
|
||||
'custom'
|
||||
)
|
||||
);
|
||||
end $$;
|
||||
Reference in New Issue
Block a user