forked from wangziqi/gongxue-base
feat: enforce practice access controls
This commit is contained in:
95
supabase/migrations/202606210011_practice_access_control.sql
Normal file
95
supabase/migrations/202606210011_practice_access_control.sql
Normal file
@@ -0,0 +1,95 @@
|
||||
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 $$;
|
||||
Reference in New Issue
Block a user