Files
gongxue-base/supabase/migrations/202606210011_practice_access_control.sql
2026-06-28 23:23:02 +08:00

96 lines
4.3 KiB
SQL

alter table public.content_nodes
add column if not exists access_rules jsonb not null default '{}'::jsonb;
alter table public.question_collections
add column if not exists access_rules jsonb not null default '{}'::jsonb;
alter table public.practice_blueprints
add column if not exists access_rules jsonb not null default '{}'::jsonb;
alter table public.practice_sessions
add column if not exists access_mode text not null default 'free',
add column if not exists access_entitlement_id uuid references public.entitlements(id) on delete set null,
add column if not exists consumed_free_quota integer not null default 0,
add column if not exists access_snapshot jsonb not null default '{}'::jsonb;
do $$
begin
if not exists (select 1 from pg_constraint where conname = 'practice_sessions_access_mode_check') then
alter table public.practice_sessions
add constraint practice_sessions_access_mode_check
check (access_mode in ('free', 'svip', 'staff'));
end if;
if not exists (select 1 from pg_constraint where conname = 'practice_sessions_consumed_free_quota_check') then
alter table public.practice_sessions
add constraint practice_sessions_consumed_free_quota_check
check (consumed_free_quota >= 0);
end if;
end $$;
create table if not exists public.practice_daily_usage (
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,
usage_date date not null default current_date,
scope_type text not null default 'tenant'
check (scope_type in ('tenant', 'region', 'subject', 'question_bank', 'content_entry', 'content_node', 'collection', 'blueprint')),
scope_id uuid,
free_limit integer not null default 25 check (free_limit >= 0),
used_count integer not null default 0 check (used_count >= 0),
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists public.practice_access_events (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null references public.tenants(id) on delete cascade,
user_id uuid references public.platform_users(id) on delete set null,
practice_session_id uuid references public.practice_sessions(id) on delete set null,
event_type text not null
check (event_type in ('session_created', 'session_denied', 'quota_consumed')),
access_mode text not null default 'free'
check (access_mode in ('free', 'svip', 'staff', 'denied')),
requested_count integer not null default 0 check (requested_count >= 0),
granted_count integer not null default 0 check (granted_count >= 0),
consumed_free_quota integer not null default 0 check (consumed_free_quota >= 0),
reason text,
scope_type text,
scope_id uuid,
entitlement_id uuid references public.entitlements(id) on delete set null,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now()
);
create index if not exists idx_practice_daily_usage_user
on public.practice_daily_usage(tenant_id, user_id, usage_date desc);
create unique index if not exists idx_practice_daily_usage_unique_scope
on public.practice_daily_usage(tenant_id, user_id, usage_date, scope_type, scope_id)
nulls not distinct;
create index if not exists idx_practice_access_events_user
on public.practice_access_events(tenant_id, user_id, created_at desc);
create index if not exists idx_practice_access_events_session
on public.practice_access_events(tenant_id, practice_session_id);
do $$
declare
table_name text;
begin
foreach table_name in array array['practice_daily_usage', 'practice_access_events']
loop
execute format('alter table public.%I enable row level security', table_name);
execute format('drop policy if exists tenant_isolation on public.%I', table_name);
execute format(
'create policy tenant_isolation on public.%I 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())',
table_name
);
execute format('drop trigger if exists set_updated_at on public.%I', table_name);
end loop;
execute 'create trigger set_updated_at before update on public.practice_daily_usage for each row execute function app.touch_updated_at()';
end $$;