/// // // Issue #30 — Onboarding track (5-day, theme-level introduction). // // Two collections: // onboarding_overviews — cached per-theme overview content (one row per theme), // keyed by the theme name, with a topics_fingerprint for // staleness detection / regeneration. // onboarding_completions — per-user, per-theme completion marker. // // Design notes (consistent with issues #18/#27): // * team_member_id is a plain TEXT field, NOT a relation — admins can delete // team_members, and a relation with cascadeDelete would otherwise block that // delete / drag the collection into the migration graph. Completions are keyed // by (team_member_id, theme); orphan rows after a member delete are harmless. // * API rules are set EXPLICITLY to authenticated-only. The tighten-rules // migration (1781000001) already ran against the collections that existed then, // so new collections must lock themselves down or they default to public. const AUTHED = '@request.auth.id != ""'; migrate((app) => { const overviews = new Collection({ "id": "pbc_onboarding_overviews0", "name": "onboarding_overviews", "type": "base", "system": false, "fields": [ { "autogeneratePattern": "[a-z0-9]{15}", "hidden": false, "id": "text_id_oov", "max": 15, "min": 15, "name": "id", "pattern": "^[a-z0-9]+$", "presentable": false, "primaryKey": true, "required": true, "system": true, "type": "text" }, { "system": false, "id": "text_theme_oov", "name": "theme", "type": "text", "required": true, "presentable": false, "max": 0, "min": 0, "pattern": "" }, { "system": false, "id": "json_content_oov", "name": "content", "type": "json", "required": true, "presentable": false }, { "system": false, "id": "text_fingerprint_oov", "name": "topics_fingerprint", "type": "text", "required": true, "presentable": false, "max": 0, "min": 0, "pattern": "" }, { "hidden": false, "id": "autodate_created_oov", "name": "created", "onCreate": true, "onUpdate": false, "presentable": false, "system": true, "type": "autodate" }, { "hidden": false, "id": "autodate_updated_oov", "name": "updated", "onCreate": true, "onUpdate": true, "presentable": false, "system": true, "type": "autodate" } ], "indexes": [ "CREATE UNIQUE INDEX `idx_onboarding_overviews_theme` ON `onboarding_overviews` (`theme`)" ], "listRule": AUTHED, "viewRule": AUTHED, "createRule": AUTHED, "updateRule": AUTHED, "deleteRule": AUTHED }); app.save(overviews); const completions = new Collection({ "id": "pbc_onboarding_completions0", "name": "onboarding_completions", "type": "base", "system": false, "fields": [ { "autogeneratePattern": "[a-z0-9]{15}", "hidden": false, "id": "text_id_ocp", "max": 15, "min": 15, "name": "id", "pattern": "^[a-z0-9]+$", "presentable": false, "primaryKey": true, "required": true, "system": true, "type": "text" }, { "system": false, "id": "text_ocp_team_member", "name": "team_member_id", "type": "text", "required": true, "presentable": false, "max": 0, "min": 0, "pattern": "" }, { "system": false, "id": "text_ocp_theme", "name": "theme", "type": "text", "required": true, "presentable": false, "max": 0, "min": 0, "pattern": "" }, { "hidden": false, "id": "autodate_created_ocp", "name": "created", "onCreate": true, "onUpdate": false, "presentable": false, "system": true, "type": "autodate" }, { "hidden": false, "id": "autodate_updated_ocp", "name": "updated", "onCreate": true, "onUpdate": true, "presentable": false, "system": true, "type": "autodate" } ], "indexes": [ "CREATE UNIQUE INDEX `idx_onboarding_completions_user_theme` ON `onboarding_completions` (`team_member_id`, `theme`)" ], "listRule": AUTHED, "viewRule": AUTHED, "createRule": AUTHED, "updateRule": AUTHED, "deleteRule": AUTHED }); app.save(completions); }, (app) => { const completions = app.findCollectionByNameOrId("onboarding_completions"); if (completions) { app.delete(completions); } const overviews = app.findCollectionByNameOrId("onboarding_overviews"); if (overviews) { app.delete(overviews); } })