forked from wangziqi/gongxue-base
feat: add content navigation practice assembly
This commit is contained in:
191
supabase/migrations/202606210008_content_navigation_practice.sql
Normal file
191
supabase/migrations/202606210008_content_navigation_practice.sql
Normal file
@@ -0,0 +1,191 @@
|
||||
create extension if not exists ltree;
|
||||
|
||||
create table if not exists public.content_entries (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
region_id uuid references public.regions(id) on delete set null,
|
||||
legacy_id text,
|
||||
entry_key text not null,
|
||||
name text not null,
|
||||
entry_type text not null default 'question_practice'
|
||||
check (entry_type in ('question_practice', 'vocabulary', 'handbook', 'scoreline', 'resource', 'ai_report', 'custom')),
|
||||
icon text,
|
||||
route text,
|
||||
description text,
|
||||
visibility text not null default 'public'
|
||||
check (visibility in ('public', 'members', 'svip', 'hidden')),
|
||||
access_rules jsonb not null default '{}'::jsonb,
|
||||
layout_config jsonb not null default '{}'::jsonb,
|
||||
sort_order integer not null default 0,
|
||||
is_active boolean not null default true,
|
||||
created_by uuid references public.platform_users(id) on delete set null,
|
||||
updated_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, entry_key),
|
||||
unique (tenant_id, legacy_id)
|
||||
);
|
||||
|
||||
create table if not exists public.content_nodes (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
entry_id uuid not null references public.content_entries(id) on delete cascade,
|
||||
region_id uuid references public.regions(id) on delete set null,
|
||||
parent_id uuid references public.content_nodes(id) on delete cascade,
|
||||
legacy_id text,
|
||||
node_key text,
|
||||
name text not null,
|
||||
node_type text not null default 'category'
|
||||
check (node_type in ('category', 'subject', 'chapter', 'paper', 'school', 'major', 'exam_target', 'resource_group', 'custom')),
|
||||
marker_type text
|
||||
check (marker_type is null or marker_type in ('school', 'major', 'subject', 'exam_track', 'course_package', 'sales_intent', 'custom')),
|
||||
marker_config jsonb not null default '{}'::jsonb,
|
||||
path ltree,
|
||||
depth integer not null default 0 check (depth >= 0),
|
||||
sort_order integer not null default 0,
|
||||
is_active boolean not null default true,
|
||||
is_selectable boolean not null default true,
|
||||
is_leaf boolean not null default false,
|
||||
metadata jsonb not null default '{}'::jsonb,
|
||||
created_by uuid references public.platform_users(id) on delete set null,
|
||||
updated_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, legacy_id),
|
||||
unique (tenant_id, entry_id, node_key)
|
||||
);
|
||||
|
||||
create table if not exists public.question_collections (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
region_id uuid references public.regions(id) on delete set null,
|
||||
entry_id uuid references public.content_entries(id) on delete set null,
|
||||
node_id uuid references public.content_nodes(id) on delete set null,
|
||||
subject_id uuid references public.subjects(id) on delete set null,
|
||||
category_id uuid references public.categories(id) on delete set null,
|
||||
question_bank_id uuid references public.question_banks(id) on delete set null,
|
||||
legacy_id text,
|
||||
name text not null,
|
||||
collection_type text not null default 'dynamic'
|
||||
check (collection_type in ('dynamic', 'manual', 'paper', 'chapter', 'mock_exam')),
|
||||
source_type text not null default 'filters'
|
||||
check (source_type in ('filters', 'manual_questions', 'node_descendants', 'category', 'subject', 'question_bank')),
|
||||
filters jsonb not null default '{}'::jsonb,
|
||||
question_count integer not null default 0 check (question_count >= 0),
|
||||
total_score numeric(8,2),
|
||||
duration_minutes integer check (duration_minutes is null or duration_minutes > 0),
|
||||
status text not null default 'active' check (status in ('draft', 'active', '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,
|
||||
updated_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, legacy_id)
|
||||
);
|
||||
|
||||
create table if not exists public.question_collection_items (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
collection_id uuid not null references public.question_collections(id) on delete cascade,
|
||||
question_id uuid not null references public.questions(id) on delete cascade,
|
||||
section_key text,
|
||||
sort_order integer not null default 0,
|
||||
score numeric(8,2),
|
||||
required boolean not null default true,
|
||||
metadata jsonb not null default '{}'::jsonb,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (tenant_id, collection_id, question_id)
|
||||
);
|
||||
|
||||
create table if not exists public.practice_blueprints (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
||||
region_id uuid references public.regions(id) on delete set null,
|
||||
entry_id uuid references public.content_entries(id) on delete set null,
|
||||
node_id uuid references public.content_nodes(id) on delete set null,
|
||||
collection_id uuid references public.question_collections(id) on delete set null,
|
||||
legacy_id text,
|
||||
name text not null,
|
||||
mode text not null check (mode in ('sequential', 'random', 'mock_exam', 'paper', 'wrong_review', 'favorite_review')),
|
||||
assembly_type text not null default 'collection'
|
||||
check (assembly_type in ('collection', 'node_descendants', 'manual', 'filters')),
|
||||
question_limit integer check (question_limit is null or question_limit > 0),
|
||||
duration_minutes integer check (duration_minutes is null or duration_minutes > 0),
|
||||
total_score numeric(8,2),
|
||||
pass_score numeric(8,2),
|
||||
sections jsonb not null default '[]'::jsonb,
|
||||
rules jsonb not null default '{}'::jsonb,
|
||||
status text not null default 'active' check (status in ('draft', 'active', 'archived')),
|
||||
sort_order integer not null default 0,
|
||||
created_by uuid references public.platform_users(id) on delete set null,
|
||||
updated_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, legacy_id)
|
||||
);
|
||||
|
||||
alter table public.questions
|
||||
add column if not exists entry_id uuid references public.content_entries(id) on delete set null,
|
||||
add column if not exists content_node_id uuid references public.content_nodes(id) on delete set null,
|
||||
add column if not exists primary_collection_id uuid references public.question_collections(id) on delete set null,
|
||||
add column if not exists exam_markers jsonb not null default '{}'::jsonb;
|
||||
|
||||
alter table public.content_assets
|
||||
add column if not exists entry_id uuid references public.content_entries(id) on delete set null,
|
||||
add column if not exists content_node_id uuid references public.content_nodes(id) on delete set null;
|
||||
|
||||
alter table public.content_import_jobs
|
||||
add column if not exists target_entry_id uuid references public.content_entries(id) on delete set null,
|
||||
add column if not exists target_content_node_id uuid references public.content_nodes(id) on delete set null,
|
||||
add column if not exists target_collection_id uuid references public.question_collections(id) on delete set null;
|
||||
|
||||
alter table public.practice_sessions
|
||||
add column if not exists blueprint_id uuid references public.practice_blueprints(id) on delete set null,
|
||||
add column if not exists collection_id uuid references public.question_collections(id) on delete set null,
|
||||
add column if not exists entry_id uuid references public.content_entries(id) on delete set null,
|
||||
add column if not exists content_node_id uuid references public.content_nodes(id) on delete set null,
|
||||
add column if not exists question_ids jsonb not null default '[]'::jsonb,
|
||||
add column if not exists question_count integer not null default 0,
|
||||
add column if not exists duration_minutes integer,
|
||||
add column if not exists total_score numeric(8,2),
|
||||
add column if not exists expires_at timestamptz;
|
||||
|
||||
create index if not exists idx_content_entries_tenant_region
|
||||
on public.content_entries(tenant_id, region_id, entry_type, is_active, sort_order);
|
||||
create index if not exists idx_content_nodes_tenant_entry_parent
|
||||
on public.content_nodes(tenant_id, entry_id, parent_id, sort_order);
|
||||
create index if not exists idx_content_nodes_path
|
||||
on public.content_nodes using gist(path);
|
||||
create index if not exists idx_content_nodes_marker
|
||||
on public.content_nodes(tenant_id, marker_type)
|
||||
where marker_type is not null;
|
||||
create index if not exists idx_question_collections_node
|
||||
on public.question_collections(tenant_id, node_id, status, sort_order);
|
||||
create index if not exists idx_question_collection_items_collection
|
||||
on public.question_collection_items(tenant_id, collection_id, section_key, sort_order);
|
||||
create index if not exists idx_practice_blueprints_scope
|
||||
on public.practice_blueprints(tenant_id, node_id, collection_id, mode, status, sort_order);
|
||||
create index if not exists idx_questions_navigation
|
||||
on public.questions(tenant_id, entry_id, content_node_id, primary_collection_id, status);
|
||||
|
||||
do $$
|
||||
declare
|
||||
table_name text;
|
||||
begin
|
||||
foreach table_name in array array[
|
||||
'content_entries', 'content_nodes', 'question_collections',
|
||||
'question_collection_items', 'practice_blueprints'
|
||||
]
|
||||
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);
|
||||
execute format('create trigger set_updated_at before update on public.%I for each row execute function app.touch_updated_at()', table_name);
|
||||
end loop;
|
||||
end $$;
|
||||
Reference in New Issue
Block a user