feat: enforce practice access controls

This commit is contained in:
Codex
2026-06-28 23:23:02 +08:00
parent 3647c2bc2d
commit 6a7294224d
12 changed files with 1047 additions and 110 deletions

View File

@@ -25,6 +25,10 @@ const ids = {
questionBank: '00000000-0000-0000-0000-000000000400',
question: '00000000-0000-0000-0000-000000000401',
questionVersion: '00000000-0000-0000-0000-000000000402',
questionTwo: '00000000-0000-0000-0000-000000000403',
questionTwoVersion: '00000000-0000-0000-0000-000000000404',
questionThree: '00000000-0000-0000-0000-000000000405',
questionThreeVersion: '00000000-0000-0000-0000-000000000406',
plan: '00000000-0000-0000-0000-000000000201',
order: '00000000-0000-0000-0000-000000000701',
payment: '00000000-0000-0000-0000-000000000702',
@@ -73,6 +77,97 @@ async function main() {
[tenantId, ids.user, ids.tenantOperatorUser, ids.tenantSalesUser, ids.tenantAgentUser],
);
await client.query(
`
with transient_users as (
select u.id
from public.platform_users u
where u.phone in ('13800000006', '13800000007', '13800000008')
)
delete from public.practice_access_events
where tenant_id = $1
and user_id in (select id from transient_users)
`,
[tenantId],
);
await client.query(
`
with transient_users as (
select u.id
from public.platform_users u
where u.phone in ('13800000006', '13800000007', '13800000008')
)
delete from public.practice_daily_usage
where tenant_id = $1
and user_id in (select id from transient_users)
`,
[tenantId],
);
await client.query(
`
with transient_users as (
select u.id
from public.platform_users u
where u.phone in ('13800000006', '13800000007', '13800000008')
)
delete from public.answer_records
where tenant_id = $1
and user_id in (select id from transient_users)
`,
[tenantId],
);
await client.query(
`
with transient_users as (
select u.id
from public.platform_users u
where u.phone in ('13800000006', '13800000007', '13800000008')
)
delete from public.practice_sessions
where tenant_id = $1
and user_id in (select id from transient_users)
`,
[tenantId],
);
await client.query(
`
with transient_users as (
select u.id
from public.platform_users u
where u.phone in ('13800000006', '13800000007', '13800000008')
)
delete from app_private.auth_sessions
where tenant_id = $1
and user_id in (select id from transient_users)
`,
[tenantId],
);
await client.query(
`
with transient_users as (
select u.id
from public.platform_users u
where u.phone in ('13800000006', '13800000007', '13800000008')
)
delete from public.user_identities
where user_id in (select id from transient_users)
`,
);
await client.query(
`
delete from public.sms_verification_codes
where tenant_id = $1
and phone in ('13800000006', '13800000007', '13800000008')
`,
[tenantId],
);
await client.query(
`
delete from public.referral_qrcodes
@@ -328,13 +423,14 @@ async function main() {
id, tenant_id, region_id, entry_id, node_id, subject_id, category_id,
question_bank_id, legacy_id, name, collection_type, source_type,
filters, question_count, total_score, duration_minutes, status,
sort_order, metadata, created_by, updated_by
sort_order, access_rules, metadata, created_by, updated_by
)
values (
$1, $2, $3, $4, $5, $6, $7,
$8, 'smoke-collection', '烟测学院专业课题目列表', 'manual', 'manual_questions',
'{"tabs":["all","paper","chapter","type"]}'::jsonb, 0, 100, 120, 'active',
1, '{"business":"supports sequential random mock exam"}'::jsonb, $9, $9
1, '{"freeDailyLimit":2,"freeSessionLimit":2,"freeQuotaScopeType":"tenant"}'::jsonb,
'{"business":"supports sequential random mock exam"}'::jsonb, $9, $9
)
on conflict (id)
do update set node_id = excluded.node_id,
@@ -348,6 +444,7 @@ async function main() {
total_score = excluded.total_score,
duration_minutes = excluded.duration_minutes,
status = 'active',
access_rules = excluded.access_rules,
updated_at = now()
`,
[
@@ -370,11 +467,22 @@ async function main() {
entry_id, content_node_id, primary_collection_id,
legacy_id, type, type_label, difficulty, status, exam_markers
)
values (
$1, $2, $3, $4, $5, $6, $7, $8,
'smoke-question', 'choice', '单选题', 1, 'published',
'{"examTrack":"professional","school":"烟测学院"}'::jsonb
)
values
(
$1, $2, $5, $6, $7, $8, $9, $10,
'smoke-question', 'choice', '单选题', 1, 'published',
'{"examTrack":"professional","school":"烟测学院"}'::jsonb
),
(
$3, $2, $5, $6, $7, $8, $9, $10,
'smoke-question-2', 'choice', '单选题', 1, 'published',
'{"examTrack":"professional","school":"烟测学院"}'::jsonb
),
(
$4, $2, $5, $6, $7, $8, $9, $10,
'smoke-question-3', 'choice', '单选题', 1, 'published',
'{"examTrack":"professional","school":"烟测学院"}'::jsonb
)
on conflict (id)
do update set question_bank_id = excluded.question_bank_id,
subject_id = excluded.subject_id,
@@ -388,6 +496,8 @@ async function main() {
[
ids.question,
tenantId,
ids.questionTwo,
ids.questionThree,
ids.questionBank,
ids.subject,
ids.category,
@@ -402,14 +512,17 @@ async function main() {
insert into public.question_collection_items (
tenant_id, collection_id, question_id, section_key, sort_order, score, required, metadata
)
values ($1, $2, $3, 'choice', 1, 2, true, '{"source":"smoke-seed"}'::jsonb)
values
($1, $2, $3, 'choice', 1, 2, true, '{"source":"smoke-seed"}'::jsonb),
($1, $2, $4, 'choice', 2, 2, true, '{"source":"smoke-seed"}'::jsonb),
($1, $2, $5, 'choice', 3, 2, true, '{"source":"smoke-seed"}'::jsonb)
on conflict (tenant_id, collection_id, question_id)
do update set section_key = excluded.section_key,
sort_order = excluded.sort_order,
score = excluded.score,
updated_at = now()
`,
[tenantId, ids.questionCollection, ids.question],
[tenantId, ids.questionCollection, ids.question, ids.questionTwo, ids.questionThree],
);
await client.query(
@@ -487,11 +600,22 @@ async function main() {
id, tenant_id, question_id, version_no, content, options,
correct_option_index, correct_option_indices, answer_text, explanation
)
values (
$1, $2, $3, 1, '1 + 1 = ?',
'[{"label":"A","text":"1"},{"label":"B","text":"2"},{"label":"C","text":"3"}]'::jsonb,
1, '[1]'::jsonb, '2', '基础加法。'
)
values
(
$1, $2, $4, 1, '1 + 1 = ?',
'[{"label":"A","text":"1"},{"label":"B","text":"2"},{"label":"C","text":"3"}]'::jsonb,
1, '[1]'::jsonb, '2', '基础加法。'
),
(
$3, $2, $5, 1, '2 + 2 = ?',
'[{"label":"A","text":"3"},{"label":"B","text":"4"},{"label":"C","text":"5"}]'::jsonb,
1, '[1]'::jsonb, '4', '基础加法。'
),
(
$6, $2, $7, 1, '3 + 3 = ?',
'[{"label":"A","text":"5"},{"label":"B","text":"6"},{"label":"C","text":"7"}]'::jsonb,
1, '[1]'::jsonb, '6', '基础加法。'
)
on conflict (question_id, version_no)
do update set content = excluded.content,
options = excluded.options,
@@ -500,16 +624,31 @@ async function main() {
answer_text = excluded.answer_text,
explanation = excluded.explanation
`,
[ids.questionVersion, tenantId, ids.question],
[ids.questionVersion, tenantId, ids.questionTwoVersion, ids.question, ids.questionTwo, ids.questionThreeVersion, ids.questionThree],
);
await client.query(
`
update public.questions
set current_version_id = $3, has_video_explanation = true, updated_at = now()
where tenant_id = $1 and id = $2
update public.questions q
set current_version_id = v.version_id,
has_video_explanation = case when q.id = $2::uuid then true else q.has_video_explanation end,
updated_at = now()
from (values
($2::uuid, $3::uuid),
($4::uuid, $5::uuid),
($6::uuid, $7::uuid)
) as v(question_id, version_id)
where q.tenant_id = $1 and q.id = v.question_id
`,
[tenantId, ids.question, ids.questionVersion],
[
tenantId,
ids.question,
ids.questionVersion,
ids.questionTwo,
ids.questionTwoVersion,
ids.questionThree,
ids.questionThreeVersion,
],
);
await client.query(