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

@@ -244,7 +244,7 @@ export async function contentNodesAdminRoute(ctx: RequestContext) {
marker_type as "markerType", marker_config as "markerConfig",
path::text as path, depth, sort_order as "order",
is_active as "isActive", is_selectable as "isSelectable",
is_leaf as "isLeaf", metadata, created_by as "createdBy",
is_leaf as "isLeaf", access_rules as "accessRules", metadata, created_by as "createdBy",
updated_by as "updatedBy", created_at as "createdAt",
updated_at as "updatedAt"
from public.content_nodes
@@ -299,12 +299,12 @@ export async function upsertContentNodeRoute(ctx: RequestContext) {
insert into public.content_nodes (
id, tenant_id, entry_id, region_id, parent_id, legacy_id, node_key,
name, node_type, marker_type, marker_config, path, depth, sort_order,
is_active, is_selectable, is_leaf, metadata, created_by, updated_by
is_active, is_selectable, is_leaf, access_rules, metadata, created_by, updated_by
)
values (
$1, $2, $3, $4::uuid, $5::uuid, $6, $7,
$8, $9, $10, $11::jsonb, $12::ltree, $13, $14,
$15, $16, $17, $18::jsonb, $19, $19
$15, $16, $17, $18::jsonb, $19::jsonb, $20, $20
)
on conflict (id)
do update set entry_id = excluded.entry_id,
@@ -322,6 +322,7 @@ export async function upsertContentNodeRoute(ctx: RequestContext) {
is_active = excluded.is_active,
is_selectable = excluded.is_selectable,
is_leaf = excluded.is_leaf,
access_rules = excluded.access_rules,
metadata = excluded.metadata,
updated_by = excluded.updated_by,
updated_at = now()
@@ -332,7 +333,7 @@ export async function upsertContentNodeRoute(ctx: RequestContext) {
marker_type as "markerType", marker_config as "markerConfig",
path::text as path, depth, sort_order as "order",
is_active as "isActive", is_selectable as "isSelectable",
is_leaf as "isLeaf", metadata, created_at as "createdAt",
is_leaf as "isLeaf", access_rules as "accessRules", metadata, created_at as "createdAt",
updated_at as "updatedAt"
`,
[
@@ -353,6 +354,7 @@ export async function upsertContentNodeRoute(ctx: RequestContext) {
boolValue(body.isActive, true),
boolValue(body.isSelectable, true),
boolValue(body.isLeaf, false),
jsonObjectValue(body.accessRules),
jsonObjectValue(body.metadata),
auth.userId,
],
@@ -418,7 +420,7 @@ export async function questionCollectionsAdminRoute(ctx: RequestContext) {
legacy_id as "legacyId", name, collection_type as "collectionType",
source_type as "sourceType", filters, question_count as "questionCount",
total_score as "totalScore", duration_minutes as "durationMinutes",
status, sort_order as "order", metadata,
status, sort_order as "order", access_rules as "accessRules", metadata,
created_by as "createdBy", updated_by as "updatedBy",
created_at as "createdAt", updated_at as "updatedAt"
from public.question_collections
@@ -526,13 +528,13 @@ export async function upsertQuestionCollectionRoute(ctx: RequestContext) {
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
status, sort_order, access_rules, metadata, created_by, updated_by
)
values (
coalesce($1::uuid, gen_random_uuid()), $2, $3::uuid, $4::uuid, $5::uuid, $6::uuid,
$7::uuid, $8::uuid, $9, $10, $11,
$12, $13::jsonb, $14, $15, $16,
$17, $18, $19::jsonb, $20, $20
$17, $18, $19::jsonb, $20::jsonb, $21, $21
)
on conflict (id)
do update set region_id = excluded.region_id,
@@ -551,6 +553,7 @@ export async function upsertQuestionCollectionRoute(ctx: RequestContext) {
duration_minutes = excluded.duration_minutes,
status = excluded.status,
sort_order = excluded.sort_order,
access_rules = excluded.access_rules,
metadata = excluded.metadata,
updated_by = excluded.updated_by,
updated_at = now()
@@ -561,7 +564,7 @@ export async function upsertQuestionCollectionRoute(ctx: RequestContext) {
legacy_id as "legacyId", name, collection_type as "collectionType",
source_type as "sourceType", filters, question_count as "questionCount",
total_score as "totalScore", duration_minutes as "durationMinutes",
status, sort_order as "order", metadata,
status, sort_order as "order", access_rules as "accessRules", metadata,
created_at as "createdAt", updated_at as "updatedAt"
`,
[
@@ -583,6 +586,7 @@ export async function upsertQuestionCollectionRoute(ctx: RequestContext) {
body.durationMinutes === undefined ? null : intValue(body.durationMinutes, 0),
status,
intValue(body.order, 0),
jsonObjectValue(body.accessRules),
jsonObjectValue(body.metadata),
auth.userId,
],
@@ -647,7 +651,7 @@ export async function practiceBlueprintsAdminRoute(ctx: RequestContext) {
legacy_id as "legacyId", name, mode,
assembly_type as "assemblyType", question_limit as "questionLimit",
duration_minutes as "durationMinutes", total_score as "totalScore",
pass_score as "passScore", sections, rules, status,
pass_score as "passScore", sections, rules, access_rules as "accessRules", status,
sort_order as "order", created_by as "createdBy",
updated_by as "updatedBy", created_at as "createdAt",
updated_at as "updatedAt"
@@ -686,13 +690,13 @@ export async function upsertPracticeBlueprintRoute(ctx: RequestContext) {
id, tenant_id, region_id, entry_id, node_id, collection_id,
legacy_id, name, mode, assembly_type, question_limit,
duration_minutes, total_score, pass_score, sections, rules,
status, sort_order, created_by, updated_by
access_rules, status, sort_order, created_by, updated_by
)
values (
coalesce($1::uuid, gen_random_uuid()), $2, $3::uuid, $4::uuid, $5::uuid, $6::uuid,
$7, $8, $9, $10, $11,
$12, $13, $14, $15::jsonb, $16::jsonb,
$17, $18, $19, $19
$17::jsonb, $18, $19, $20, $20
)
on conflict (id)
do update set region_id = excluded.region_id,
@@ -709,6 +713,7 @@ export async function upsertPracticeBlueprintRoute(ctx: RequestContext) {
pass_score = excluded.pass_score,
sections = excluded.sections,
rules = excluded.rules,
access_rules = excluded.access_rules,
status = excluded.status,
sort_order = excluded.sort_order,
updated_by = excluded.updated_by,
@@ -719,7 +724,7 @@ export async function upsertPracticeBlueprintRoute(ctx: RequestContext) {
legacy_id as "legacyId", name, mode,
assembly_type as "assemblyType", question_limit as "questionLimit",
duration_minutes as "durationMinutes", total_score as "totalScore",
pass_score as "passScore", sections, rules, status,
pass_score as "passScore", sections, rules, access_rules as "accessRules", status,
sort_order as "order", created_at as "createdAt",
updated_at as "updatedAt"
`,
@@ -740,6 +745,7 @@ export async function upsertPracticeBlueprintRoute(ctx: RequestContext) {
body.passScore === undefined || body.passScore === null ? null : Number(body.passScore),
jsonArrayValue(body.sections),
jsonObjectValue(body.rules),
jsonObjectValue(body.accessRules),
status,
intValue(body.order, 0),
auth.userId,