forked from wangziqi/gongxue-base
165 lines
8.1 KiB
SQL
165 lines
8.1 KiB
SQL
alter table public.content_assets
|
|
add column if not exists asset_type text not null default 'document',
|
|
add column if not exists storage_provider text not null default 'external_url',
|
|
add column if not exists bucket text,
|
|
add column if not exists object_key text,
|
|
add column if not exists mime_type text,
|
|
add column if not exists file_size_bytes bigint,
|
|
add column if not exists checksum_sha256 text,
|
|
add column if not exists visibility text not null default 'tenant',
|
|
add column if not exists region_id uuid references public.regions(id) on delete set null,
|
|
add column if not exists subject_id uuid references public.subjects(id) on delete set null,
|
|
add column if not exists category_id uuid references public.categories(id) on delete set null,
|
|
add column if not exists node_id uuid references public.module_nodes(id) on delete set null,
|
|
add column if not exists preview_url text,
|
|
add column if not exists status text not null default 'active',
|
|
add column if not exists sort_order integer not null default 0,
|
|
add column if not exists access_rules jsonb not null default '{}'::jsonb,
|
|
add column if not exists created_by uuid references public.platform_users(id) on delete set null,
|
|
add column if not exists updated_by uuid references public.platform_users(id) on delete set null,
|
|
add column if not exists source text not null default 'manual',
|
|
add column if not exists download_count integer not null default 0;
|
|
|
|
update public.content_assets
|
|
set visibility = case when is_public then 'public' else visibility end,
|
|
storage_provider = case when cdn_url is not null and cdn_url <> '' then 'external_url' else storage_provider end
|
|
where visibility = 'tenant' or storage_provider = 'external_url';
|
|
|
|
do $$
|
|
begin
|
|
if not exists (select 1 from pg_constraint where conname = 'content_assets_asset_type_check') then
|
|
alter table public.content_assets
|
|
add constraint content_assets_asset_type_check
|
|
check (asset_type in ('pdf', 'video', 'image', 'audio', 'document', 'package', 'link', 'other'));
|
|
end if;
|
|
|
|
if not exists (select 1 from pg_constraint where conname = 'content_assets_storage_provider_check') then
|
|
alter table public.content_assets
|
|
add constraint content_assets_storage_provider_check
|
|
check (storage_provider in ('external_url', 'supabase_storage', 'aliyun_oss', 'tencent_cos', 'qiniu_kodo', 'local_dev'));
|
|
end if;
|
|
|
|
if not exists (select 1 from pg_constraint where conname = 'content_assets_visibility_check') then
|
|
alter table public.content_assets
|
|
add constraint content_assets_visibility_check
|
|
check (visibility in ('public', 'tenant', 'members', 'svip', 'private'));
|
|
end if;
|
|
|
|
if not exists (select 1 from pg_constraint where conname = 'content_assets_status_check') then
|
|
alter table public.content_assets
|
|
add constraint content_assets_status_check
|
|
check (status in ('draft', 'active', 'archived'));
|
|
end if;
|
|
|
|
if not exists (select 1 from pg_constraint where conname = 'content_assets_file_size_check') then
|
|
alter table public.content_assets
|
|
add constraint content_assets_file_size_check
|
|
check (file_size_bytes is null or file_size_bytes >= 0);
|
|
end if;
|
|
|
|
if not exists (select 1 from pg_constraint where conname = 'content_assets_download_count_check') then
|
|
alter table public.content_assets
|
|
add constraint content_assets_download_count_check
|
|
check (download_count >= 0);
|
|
end if;
|
|
end $$;
|
|
|
|
create table if not exists public.content_import_jobs (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
created_by uuid references public.platform_users(id) on delete set null,
|
|
import_type text not null check (import_type in ('questions', 'vocabulary', 'handbook', 'scoreline', 'assets', 'videos')),
|
|
source_format text not null default 'json' check (source_format in ('json', 'excel', 'csv', 'pocketbase', 'api')),
|
|
status text not null default 'preview' check (status in ('preview', 'pending', 'importing', 'completed', 'completed_with_errors', 'failed', 'rejected')),
|
|
source_name text,
|
|
source_hash text,
|
|
target_region_id uuid references public.regions(id) on delete set null,
|
|
target_subject_id uuid references public.subjects(id) on delete set null,
|
|
target_category_id uuid references public.categories(id) on delete set null,
|
|
target_node_id uuid references public.module_nodes(id) on delete set null,
|
|
target_question_bank_id uuid references public.question_banks(id) on delete set null,
|
|
dry_run boolean not null default true,
|
|
total_count integer not null default 0,
|
|
valid_count integer not null default 0,
|
|
error_count integer not null default 0,
|
|
warning_count integer not null default 0,
|
|
inserted_count integer not null default 0,
|
|
updated_count integer not null default 0,
|
|
skipped_count integer not null default 0,
|
|
summary jsonb not null default '{}'::jsonb,
|
|
raw_payload jsonb not null default '[]'::jsonb,
|
|
normalized_payload jsonb not null default '[]'::jsonb,
|
|
error_message text,
|
|
started_at timestamptz,
|
|
finished_at timestamptz,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists public.content_import_items (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
job_id uuid not null references public.content_import_jobs(id) on delete cascade,
|
|
row_no integer not null,
|
|
external_id text,
|
|
status text not null default 'valid' check (status in ('valid', 'invalid', 'inserted', 'updated', 'skipped', 'failed')),
|
|
target_type text,
|
|
target_id uuid,
|
|
source_payload jsonb not null default '{}'::jsonb,
|
|
normalized_payload jsonb not null default '{}'::jsonb,
|
|
content_hash text,
|
|
issues_count integer not null default 0,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
unique (job_id, row_no)
|
|
);
|
|
|
|
create table if not exists public.content_import_issues (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
job_id uuid not null references public.content_import_jobs(id) on delete cascade,
|
|
item_id uuid references public.content_import_items(id) on delete cascade,
|
|
row_no integer,
|
|
severity text not null default 'error' check (severity in ('error', 'warning')),
|
|
code text not null,
|
|
field_path text,
|
|
message text not null,
|
|
details jsonb not null default '{}'::jsonb,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists idx_content_assets_filters
|
|
on public.content_assets(tenant_id, status, visibility, asset_type, region_id, subject_id, category_id, sort_order);
|
|
create index if not exists idx_content_assets_object
|
|
on public.content_assets(tenant_id, storage_provider, bucket, object_key);
|
|
create index if not exists idx_content_assets_checksum
|
|
on public.content_assets(tenant_id, checksum_sha256)
|
|
where checksum_sha256 is not null;
|
|
|
|
create index if not exists idx_content_import_jobs_tenant_status
|
|
on public.content_import_jobs(tenant_id, import_type, status, created_at desc);
|
|
create index if not exists idx_content_import_items_job_status
|
|
on public.content_import_items(tenant_id, job_id, status, row_no);
|
|
create index if not exists idx_content_import_issues_job
|
|
on public.content_import_issues(tenant_id, job_id, severity, row_no);
|
|
|
|
do $$
|
|
declare
|
|
table_name text;
|
|
begin
|
|
foreach table_name in array array[
|
|
'content_import_jobs', 'content_import_items', 'content_import_issues'
|
|
]
|
|
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 $$;
|