spine-framework 0.3.124 → 0.3.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.framework/cli/commands/agents.ts +35 -37
- package/.framework/cli/commands/auth.ts +35 -34
- package/.framework/cli/commands/create-app.ts +104 -40
- package/.framework/cli/commands/dev.ts +104 -69
- package/.framework/cli/commands/doctor.ts +179 -69
- package/.framework/cli/commands/generate.ts +83 -143
- package/.framework/cli/commands/init.ts +72 -34
- package/.framework/cli/commands/install-app.ts +260 -125
- package/.framework/cli/commands/items.ts +33 -44
- package/.framework/cli/commands/migrate.ts +122 -40
- package/.framework/cli/commands/migrations.ts +33 -40
- package/.framework/cli/commands/pipelines.ts +31 -39
- package/.framework/cli/commands/status.ts +47 -23
- package/.framework/cli/commands/system.ts +47 -37
- package/.framework/cli/commands/test.ts +112 -68
- package/.framework/cli/commands/validate-app.ts +215 -121
- package/.framework/cli/context.ts +112 -130
- package/.framework/cli/env-loader.ts +47 -18
- package/.framework/cli/index.ts +81 -42
- package/.framework/docs/runtime-types.md +1024 -0
- package/.framework/functions/_shared/agent-runner.ts +1296 -961
- package/.framework/functions/_shared/app-manifest.ts +82 -68
- package/.framework/functions/_shared/audit.ts +135 -83
- package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
- package/.framework/functions/_shared/db.ts +80 -126
- package/.framework/functions/_shared/hash-api-key.ts +63 -0
- package/.framework/functions/_shared/index.ts +224 -335
- package/.framework/functions/_shared/kb.ts +319 -0
- package/.framework/functions/_shared/llm.ts +92 -0
- package/.framework/functions/_shared/logging.ts +288 -0
- package/.framework/functions/_shared/middleware.ts +329 -365
- package/.framework/functions/_shared/outcomes.ts +36 -0
- package/.framework/functions/_shared/permissions.ts +359 -643
- package/.framework/functions/_shared/pipeline-runner.ts +243 -316
- package/.framework/functions/_shared/principal.ts +376 -468
- package/.framework/functions/_shared/resolve-credentials.ts +102 -0
- package/.framework/functions/_shared/resolve-ids.ts +188 -16
- package/.framework/functions/_shared/retry-deliveries.ts +104 -0
- package/.framework/functions/_shared/schema-utils.ts +611 -373
- package/.framework/functions/_shared/testing.ts +207 -137
- package/.framework/functions/_shared/tracing.ts +276 -0
- package/.framework/functions/_shared/trigger-engine.ts +159 -211
- package/.framework/functions/_shared/webhook-registration.ts +95 -81
- package/.framework/functions/_shared/webhook-registry.ts +153 -75
- package/.framework/functions/account-nodes.ts +31 -53
- package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
- package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
- package/.framework/functions/adapters/openai-outbound.ts +40 -0
- package/.framework/functions/admin-data.ts +685 -537
- package/.framework/functions/admin-funnel.ts +31 -20
- package/.framework/functions/admin-install-funnel.ts +47 -28
- package/.framework/functions/admin-signals.ts +47 -29
- package/.framework/functions/ai-agents.ts +122 -147
- package/.framework/functions/ai-interactions.ts +180 -90
- package/.framework/functions/api-keys.ts +250 -192
- package/.framework/functions/apps.ts +203 -197
- package/.framework/functions/auth.ts +98 -91
- package/.framework/functions/cil-agent-ingest.ts +773 -0
- package/.framework/functions/cil-agent.ts +592 -0
- package/.framework/functions/cil-callmap.ts +1227 -0
- package/.framework/functions/cil-config.ts +583 -0
- package/.framework/functions/cil-context.ts +445 -0
- package/.framework/functions/cil-doc.ts +1752 -0
- package/.framework/functions/cil-embeddings.ts +1202 -0
- package/.framework/functions/cil-graph.ts +585 -0
- package/.framework/functions/cil-ingest.ts +2416 -0
- package/.framework/functions/cil-pipeline.ts +318 -0
- package/.framework/functions/cil-project.ts +499 -0
- package/.framework/functions/cil-validate.ts +469 -0
- package/.framework/functions/debug-auth.ts +73 -53
- package/.framework/functions/deliveries.ts +117 -0
- package/.framework/functions/embeddings.ts +528 -516
- package/.framework/functions/eval-harness.ts +158 -106
- package/.framework/functions/integration-outbound.ts +311 -0
- package/.framework/functions/integration-routes.ts +240 -466
- package/.framework/functions/integrations.ts +132 -140
- package/.framework/functions/invites.ts +401 -201
- package/.framework/functions/item-progress.ts +113 -53
- package/.framework/functions/kb.ts +28 -0
- package/.framework/functions/logs.ts +239 -222
- package/.framework/functions/observability.ts +308 -259
- package/.framework/functions/pipeline-executions.ts +179 -235
- package/.framework/functions/pipelines.ts +163 -205
- package/.framework/functions/prompt-configs.ts +109 -156
- package/.framework/functions/roles.ts +97 -147
- package/.framework/functions/system-cron.ts +132 -218
- package/.framework/functions/system.ts +112 -52
- package/.framework/functions/tests.ts +32 -28
- package/.framework/functions/timers.ts +162 -210
- package/.framework/functions/triggers.ts +282 -346
- package/.framework/functions/types.ts +283 -400
- package/.framework/migrations/001_core_schema.sql +630 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
- package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
- package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
- package/.framework/migrations/005_core_observability.sql +598 -0
- package/.framework/migrations/006_security_hardening_rls.sql +97 -0
- package/.framework/migrations/007_kb_article_list.sql +237 -0
- package/.framework/migrations/008_items_target.sql +8 -0
- package/.framework/migrations/009_roles_slug_unique.sql +11 -0
- package/.framework/public/spine.config.json +91 -0
- package/.framework/scripts/app-install-cli.ts +112 -98
- package/.framework/src/App.tsx +67 -20
- package/.framework/src/apps/admin/index.tsx +97 -27
- package/.framework/src/components/AppWrapper.tsx +50 -11
- package/.framework/src/components/CustomAppLoader.tsx +131 -22
- package/.framework/src/components/admin/AdminListPage.tsx +75 -8
- package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
- package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
- package/.framework/src/components/admin/PageInstructions.tsx +49 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
- package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
- package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
- package/.framework/src/components/app-sidebar.tsx +50 -3
- package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
- package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
- package/.framework/src/components/kb/KBArticleList.tsx +557 -0
- package/.framework/src/components/layout/AppShell.tsx +54 -16
- package/.framework/src/components/layout/Header.tsx +50 -38
- package/.framework/src/components/layout/Layout.tsx +48 -32
- package/.framework/src/components/layout/Sidebar.tsx +167 -16
- package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
- package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
- package/.framework/src/components/runtime/DataFilters.tsx +78 -1
- package/.framework/src/components/runtime/DataHeader.tsx +32 -29
- package/.framework/src/components/runtime/DataListPage.tsx +161 -2
- package/.framework/src/components/runtime/DataStats.tsx +48 -1
- package/.framework/src/components/runtime/DataTable.tsx +153 -1
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
- package/.framework/src/components/runtime/index.ts +16 -9
- package/.framework/src/components/search-form.tsx +33 -1
- package/.framework/src/components/shared/AgentView.tsx +48 -51
- package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
- package/.framework/src/components/shared/InviteManager.tsx +113 -52
- package/.framework/src/components/shared/MemberList.tsx +182 -0
- package/.framework/src/components/shared/SchemaFields.tsx +96 -78
- package/.framework/src/components/ui/DataTable.tsx +86 -56
- package/.framework/src/components/ui/Form.tsx +188 -109
- package/.framework/src/components/ui/ItemCard.tsx +86 -58
- package/.framework/src/components/ui/ItemListView.tsx +88 -61
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
- package/.framework/src/components/ui/Modal.tsx +81 -4
- package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
- package/.framework/src/components/ui/accordion.tsx +97 -5
- package/.framework/src/components/ui/alert-dialog.tsx +225 -13
- package/.framework/src/components/ui/alert.tsx +113 -6
- package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
- package/.framework/src/components/ui/avatar.tsx +129 -7
- package/.framework/src/components/ui/badge.tsx +65 -3
- package/.framework/src/components/ui/breadcrumb.tsx +145 -8
- package/.framework/src/components/ui/button-group.tsx +81 -4
- package/.framework/src/components/ui/button.tsx +49 -2
- package/.framework/src/components/ui/calendar.tsx +65 -3
- package/.framework/src/components/ui/card.tsx +145 -8
- package/.framework/src/components/ui/carousel.tsx +129 -7
- package/.framework/src/components/ui/chart.tsx +161 -10
- package/.framework/src/components/ui/checkbox.tsx +49 -2
- package/.framework/src/components/ui/collapsible.tsx +81 -5
- package/.framework/src/components/ui/combobox.tsx +305 -20
- package/.framework/src/components/ui/command.tsx +177 -11
- package/.framework/src/components/ui/context-menu.tsx +273 -17
- package/.framework/src/components/ui/dialog.tsx +209 -27
- package/.framework/src/components/ui/direction.tsx +64 -4
- package/.framework/src/components/ui/drawer.tsx +193 -12
- package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
- package/.framework/src/components/ui/empty.tsx +145 -9
- package/.framework/src/components/ui/field.tsx +209 -14
- package/.framework/src/components/ui/hover-card.tsx +81 -4
- package/.framework/src/components/ui/input-group.tsx +162 -10
- package/.framework/src/components/ui/input-otp.tsx +98 -5
- package/.framework/src/components/ui/input.tsx +49 -2
- package/.framework/src/components/ui/item.tsx +225 -13
- package/.framework/src/components/ui/kbd.tsx +65 -3
- package/.framework/src/components/ui/label.tsx +49 -2
- package/.framework/src/components/ui/menubar.tsx +289 -17
- package/.framework/src/components/ui/native-select.tsx +97 -5
- package/.framework/src/components/ui/navigation-menu.tsx +177 -10
- package/.framework/src/components/ui/pagination.tsx +161 -9
- package/.framework/src/components/ui/popover.tsx +145 -8
- package/.framework/src/components/ui/progress.tsx +49 -2
- package/.framework/src/components/ui/radio-group.tsx +65 -3
- package/.framework/src/components/ui/resizable.tsx +82 -4
- package/.framework/src/components/ui/scroll-area.tsx +65 -3
- package/.framework/src/components/ui/select.tsx +193 -11
- package/.framework/src/components/ui/separator.tsx +49 -2
- package/.framework/src/components/ui/sheet.tsx +193 -11
- package/.framework/src/components/ui/sidebar.tsx +481 -38
- package/.framework/src/components/ui/skeleton.tsx +49 -2
- package/.framework/src/components/ui/slider.tsx +49 -2
- package/.framework/src/components/ui/sonner.tsx +49 -2
- package/.framework/src/components/ui/spinner.tsx +49 -2
- package/.framework/src/components/ui/switch.tsx +49 -2
- package/.framework/src/components/ui/table-primitives.tsx +160 -33
- package/.framework/src/components/ui/table.tsx +161 -9
- package/.framework/src/components/ui/tabs.tsx +113 -6
- package/.framework/src/components/ui/textarea.tsx +49 -2
- package/.framework/src/components/ui/toggle-group.tsx +81 -4
- package/.framework/src/components/ui/toggle.tsx +65 -3
- package/.framework/src/components/ui/tooltip.tsx +97 -5
- package/.framework/src/contexts/AppContext.tsx +129 -45
- package/.framework/src/contexts/AuthContext.tsx +187 -144
- package/.framework/src/hooks/use-mobile.ts +48 -2
- package/.framework/src/hooks/useApi.ts +208 -159
- package/.framework/src/hooks/useAppPath.ts +32 -23
- package/.framework/src/hooks/useApps.ts +51 -20
- package/.framework/src/hooks/useEntityList.ts +32 -78
- package/.framework/src/hooks/useEntityRecord.ts +35 -96
- package/.framework/src/hooks/useForm.ts +32 -92
- package/.framework/src/hooks/useListSchema.ts +49 -78
- package/.framework/src/hooks/useSchemaRecord.ts +114 -105
- package/.framework/src/lib/api.ts +195 -116
- package/.framework/src/lib/supabase.ts +98 -64
- package/.framework/src/lib/utils.ts +287 -151
- package/.framework/src/main.tsx +49 -3
- package/.framework/src/pages/DashboardPage.tsx +52 -29
- package/.framework/src/pages/NotFoundPage.tsx +32 -10
- package/.framework/src/pages/SetupPage.tsx +32 -12
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
- package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
- package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
- package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
- package/.framework/src/pages/admin/AppsPage.tsx +49 -2
- package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
- package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
- package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
- package/.framework/src/pages/admin/LogsPage.tsx +422 -75
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
- package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
- package/.framework/src/pages/admin/RolesPage.tsx +171 -18
- package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
- package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
- package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
- package/.framework/src/pages/admin/TimersPage.tsx +48 -20
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
- package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
- package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
- package/.framework/src/pages/admin/TypesPage.tsx +48 -20
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
- package/.framework/src/pages/auth/LoginPage.tsx +50 -23
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
- package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
- package/.framework/src/types/auth.ts +128 -71
- package/.framework/src/types/types.ts +373 -157
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
- package/docs/README.md +9 -130
- package/netlify.toml +5 -2
- package/package.json +13 -25
- package/scripts/kb-ingest-docs.ts +203 -0
- package/spine.config.json +65 -4
- package/.framework/migrations/001_schema.sql +0 -425
- package/.framework/migrations/002_rls_and_functions.sql +0 -763
- package/.framework/migrations/004_enhancements.sql +0 -43
- package/.framework/migrations/005_auth_user_trigger.sql +0 -105
- package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
- package/.framework/migrations/016_invites.sql +0 -27
- package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
- package/.framework/migrations/018_apps_required_roles.sql +0 -17
- package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
- package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
- package/.framework/migrations/021_app_domains.sql +0 -112
- package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
- package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
- package/.framework/migrations/024_messages_account_id.sql +0 -9
- package/.framework/migrations/025_retype_threads_messages.sql +0 -240
- package/.framework/migrations/026_fix_rls_policies.sql +0 -47
- package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
- package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
- package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
- package/.framework/migrations/030_messages_updated_by.sql +0 -6
- package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
- package/docs/APPS.md +0 -232
- package/docs/ARCHITECTURE.md +0 -246
- package/docs/INSTALL.md +0 -155
- package/docs/design-schema-spec.md +0 -1837
- package/docs/dev-guides/01-create-an-app.md +0 -183
- package/docs/dev-guides/02-create-a-function.md +0 -178
- package/docs/dev-guides/03-create-a-component.md +0 -178
- package/docs/dev-guides/04-webhook-handlers.md +0 -129
- package/docs/dev-guides/05-testing.md +0 -168
- package/docs/dev-guides/README.md +0 -40
- package/docs/npm-packages-plan.md +0 -288
- package/docs/spine-core--api-reference.md +0 -822
|
@@ -1,83 +1,97 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module invites
|
|
3
|
-
* @audience core-contributor
|
|
4
|
-
* @layer api-handler
|
|
5
|
-
* @stability stable
|
|
6
|
-
*
|
|
7
|
-
* Invite and access-request management. All invite/access-request state is
|
|
8
|
-
* stored directly on `people` rows — no separate table. Invite metadata lives
|
|
9
|
-
* in `people.data` under `invite_*` keys.
|
|
10
|
-
*
|
|
11
|
-
* **Routed by:** `GET/POST /.netlify/functions/invites?action=<action>`
|
|
12
|
-
*
|
|
13
|
-
* **Public actions (no auth required):**
|
|
14
|
-
* | action | method | description |
|
|
15
|
-
* |-------------------|--------|-------------|
|
|
16
|
-
* | check-first-user | GET | Boolean: is this the first user registration? |
|
|
17
|
-
* | check-domain | GET | Boolean: does this email domain have existing accounts? |
|
|
18
|
-
* | validate-token | GET | Validate invite token, return preview |
|
|
19
|
-
* | complete-registration | POST | Finalise walk-up registration after Supabase auth |
|
|
20
|
-
*
|
|
21
|
-
* **Protected actions (require *_admin or system_admin):**
|
|
22
|
-
* | action | method | description |
|
|
23
|
-
* |----------------|--------|-------------|
|
|
24
|
-
* | create-invite | POST | Create invite stub + return shareable URL |
|
|
25
|
-
* | list-invites | GET | List invites scoped to account (or all for system_admin) |
|
|
26
|
-
* | revoke-invite | POST | Mark invite inactive |
|
|
27
|
-
* | reinvite | POST | Issue new token on existing stub |
|
|
28
|
-
* | block-invite | POST | Manually block an invite token |
|
|
29
|
-
* | approve | POST | Approve access_request → activate person |
|
|
30
|
-
* | deny | POST | Deny access_request → deactivate person |
|
|
31
|
-
*
|
|
32
|
-
* **Invite metadata in people.data:**
|
|
33
|
-
* ```json
|
|
34
|
-
* {
|
|
35
|
-
* "invite_token": "abc123...",
|
|
36
|
-
* "invite_kind": "invite" | "access_request",
|
|
37
|
-
* "invite_expires_at": "2026-06-24T...",
|
|
38
|
-
* "invite_attempts": 0,
|
|
39
|
-
* "invite_blocked_at": null,
|
|
40
|
-
* "invite_invited_by": "person-uuid"
|
|
41
|
-
* }
|
|
42
|
-
* ```
|
|
43
|
-
*
|
|
44
|
-
* **people.status values used here:**
|
|
45
|
-
* invited — unclaimed admin invite
|
|
46
|
-
* pending — walk-up access request awaiting approval
|
|
47
|
-
* active — approved / invite redeemed
|
|
48
|
-
* inactive — denied / revoked
|
|
49
|
-
*
|
|
50
|
-
* @seeAlso migrations/016_invites.sql (indexes)
|
|
51
|
-
* @seeAlso migrations/005_auth_user_trigger.sql (stub resolution on signup)
|
|
52
|
-
* @seeAlso src/pages/auth/RegisterPage.tsx (frontend)
|
|
53
|
-
* @seeAlso src/components/shared/InviteManager.tsx (UI)
|
|
54
|
-
*/
|
|
55
1
|
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
// CIL[BEGIN chunk_id="invites_moduleheader_1783968140" type="module_header" identity="imports" parent="none"]
|
|
18
|
+
// CIL[DOC-MACRO purpose="Import dependencies for invite management API"]
|
|
19
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Import handler utilities and database access"]
|
|
20
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/functions/_shared/schema-utils.ts\",\"custom/apps/portal/pages/ContentPage.tsx\",\"\\\"]"]
|
|
21
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Imported modules" errors="Import failures" side_effects="none"]
|
|
22
|
+
// CIL[DOC-MICRO logic_summary="Imports createHandler, createPublicHandler, adminDb, emitLog" notable_decisions="[]"]
|
|
23
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
24
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
25
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
26
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, .framework/functions/_shared/schema-utils.ts, custom/apps/portal/pages/ContentPage.tsx, \."]
|
|
27
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
28
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
29
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
30
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
31
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
56
32
|
import { createHandler, createPublicHandler } from './_shared/middleware'
|
|
57
33
|
import { adminDb } from './_shared/db'
|
|
58
34
|
import { emitLog } from './_shared/audit'
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
35
|
+
import { create as adminDataCreate, list as adminDataList, update as adminDataUpdate } from './admin-data'
|
|
36
|
+
// CIL[END chunk_id="invites_moduleheader_1783968140"]
|
|
37
|
+
// CIL[BEGIN chunk_id="invites_constants_1783968140" type="constants" identity="constants" parent="none"]
|
|
38
|
+
// CIL[DOC-MACRO purpose="Define constants for invite token generation and validation"]
|
|
39
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Configure invite security parameters and domain exclusions"]
|
|
40
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[\".framework/functions/_shared/schema-utils.ts\",\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
|
|
41
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Constants" errors="none" side_effects="none"]
|
|
42
|
+
// CIL[DOC-MICRO logic_summary="Sets token length, max attempts, expiry days, and personal email domains" notable_decisions="[]"]
|
|
43
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
44
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
45
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/schema-utils.ts, custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/principal.ts, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
|
|
46
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
47
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
48
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
49
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
50
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
62
51
|
const TOKEN_LENGTH = 16
|
|
63
52
|
const MAX_ATTEMPTS = 5
|
|
64
53
|
const INVITE_EXPIRY_DAYS = 7
|
|
65
54
|
|
|
66
|
-
// Personal/free email domains — excluded from domain-match logic
|
|
67
55
|
const PERSONAL_DOMAINS = new Set([
|
|
68
56
|
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com',
|
|
69
57
|
'aol.com', 'protonmail.com', 'me.com', 'mac.com', 'live.com',
|
|
70
58
|
'msn.com', 'ymail.com', 'googlemail.com', 'fastmail.com',
|
|
71
59
|
])
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
|
|
60
|
+
// CIL[END chunk_id="invites_constants_1783968140"]
|
|
61
|
+
// CIL[BEGIN chunk_id="invites_generatetoken_1783968140" type="function" identity="generateToken" parent="none"]
|
|
62
|
+
// CIL[DOC-MACRO purpose="Generate cryptographically secure random token for invites"]
|
|
63
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Create unique invite tokens"]
|
|
64
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
|
|
65
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Random token string" errors="Crypto API failures" side_effects="none"]
|
|
66
|
+
// CIL[DOC-MICRO logic_summary="Uses crypto.getRandomValues to generate alphanumeric token" notable_decisions="[]"]
|
|
67
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
68
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
69
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 1. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/pages/admin/RolesPage.tsx, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/schema-utils.ts, \."]
|
|
70
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
|
|
71
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
72
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
73
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
74
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
75
75
|
function generateToken(): string {
|
|
76
76
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
77
77
|
const bytes = crypto.getRandomValues(new Uint8Array(TOKEN_LENGTH))
|
|
78
78
|
return Array.from(bytes).map(b => chars[b % chars.length]).join('')
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
// CIL[END chunk_id="invites_generatetoken_1783968140"]
|
|
81
|
+
// CIL[BEGIN chunk_id="invites_generateaccountslug_1783968140" type="function" identity="generateAccountSlug" parent="none"]
|
|
82
|
+
// CIL[DOC-MACRO purpose="Generate unique account slug from name with random suffix"]
|
|
83
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Create URL-safe account identifiers"]
|
|
84
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[\".framework/src/pages/admin/RolesPage.tsx\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/components/ui/dialog.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/src/components/ui/table-primitives.tsx\",\"\\\"]"]
|
|
85
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Unique account slug" errors="Invalid name format" side_effects="none"]
|
|
86
|
+
// CIL[DOC-MICRO logic_summary="Normalizes name to lowercase, adds hyphens, appends random hash" notable_decisions="[]"]
|
|
87
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
88
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
89
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/RolesPage.tsx, .framework/functions/_shared/schema-utils.ts, .framework/src/components/ui/dialog.tsx, .framework/functions/_shared/principal.ts, .framework/src/components/ui/table-primitives.tsx, \."]
|
|
90
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
91
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
92
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
93
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
94
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
81
95
|
function generateAccountSlug(name: string): string {
|
|
82
96
|
const base = name
|
|
83
97
|
.toLowerCase()
|
|
@@ -87,20 +101,42 @@ function generateAccountSlug(name: string): string {
|
|
|
87
101
|
const hash = Math.random().toString(36).slice(2, 8)
|
|
88
102
|
return `${base}-${hash}`
|
|
89
103
|
}
|
|
90
|
-
|
|
104
|
+
// CIL[END chunk_id="invites_generateaccountslug_1783968140"]
|
|
105
|
+
// CIL[BEGIN chunk_id="invites_isadminrole_1783968140" type="function" identity="isAdminRole" parent="none"]
|
|
106
|
+
// CIL[DOC-MACRO purpose="Check if role slug indicates admin privileges"]
|
|
107
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Validate admin role permissions"]
|
|
108
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[\".framework/src/pages/admin/RolesPage.tsx\",\".framework/migrations/015_simplify_apps_table.sql\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/pages/admin/IntegrationsPage.tsx\",\"\\\"]"]
|
|
109
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="boolean - admin status" errors="none" side_effects="none"]
|
|
110
|
+
// CIL[DOC-MICRO logic_summary="Checks for system_admin or role ending with _admin" notable_decisions="[]"]
|
|
111
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
112
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
113
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/RolesPage.tsx, .framework/migrations/015_simplify_apps_table.sql, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/pages/admin/IntegrationsPage.tsx, \."]
|
|
114
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
115
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
116
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
117
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
118
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
91
119
|
function isAdminRole(roleSlug: string | undefined): boolean {
|
|
92
120
|
return !!roleSlug && (
|
|
93
121
|
roleSlug === 'system_admin' ||
|
|
94
122
|
roleSlug.endsWith('_admin')
|
|
95
123
|
)
|
|
96
124
|
}
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
125
|
+
// CIL[END chunk_id="invites_isadminrole_1783968140"]
|
|
126
|
+
// CIL[BEGIN chunk_id="invites_checkfirstuser_1783968140" type="function" identity="checkFirstUser" parent="none"]
|
|
127
|
+
// CIL[DOC-MACRO purpose="Check if system has any users - enables first-user flow"]
|
|
128
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Determine if this is initial system setup"]
|
|
129
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
130
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="{is_first_user: boolean}" errors="DB query failures" side_effects="DB read operation"]
|
|
131
|
+
// CIL[DOC-MICRO logic_summary="Counts people records, returns true if count is zero" notable_decisions="[]"]
|
|
132
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
133
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
134
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
135
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
136
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
137
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
138
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
139
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
104
140
|
export const checkFirstUser = createHandler(async (_ctx, _body) => {
|
|
105
141
|
const { count, error } = await adminDb
|
|
106
142
|
.from('people')
|
|
@@ -110,14 +146,21 @@ export const checkFirstUser = createHandler(async (_ctx, _body) => {
|
|
|
110
146
|
|
|
111
147
|
return { is_first_user: (count ?? 0) === 0 }
|
|
112
148
|
})
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
149
|
+
// CIL[END chunk_id="invites_checkfirstuser_1783968140"]
|
|
150
|
+
// CIL[BEGIN chunk_id="invites_checkdomain_1783968140" type="function" identity="checkDomain" parent="none"]
|
|
151
|
+
// CIL[DOC-MACRO purpose="Check if email domain has existing active accounts"]
|
|
152
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Validate domain for account matching logic"]
|
|
153
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
154
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="{match: boolean}" errors="Invalid email, DB errors" side_effects="DB read operation"]
|
|
155
|
+
// CIL[DOC-MICRO logic_summary="Excludes personal domains, checks for active accounts with same domain" notable_decisions="[]"]
|
|
156
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
157
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
158
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
159
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
160
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
161
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
162
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
163
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
121
164
|
export const checkDomain = createHandler(async (_ctx, _body) => {
|
|
122
165
|
const email = _ctx.query?.email as string | undefined
|
|
123
166
|
if (!email || !email.includes('@')) {
|
|
@@ -139,13 +182,21 @@ export const checkDomain = createHandler(async (_ctx, _body) => {
|
|
|
139
182
|
|
|
140
183
|
return { match: (data?.length ?? 0) > 0 }
|
|
141
184
|
})
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
185
|
+
// CIL[END chunk_id="invites_checkdomain_1783968140"]
|
|
186
|
+
// CIL[BEGIN chunk_id="invites_validatetoken_1783968140" type="function" identity="validateToken" parent="none"]
|
|
187
|
+
// CIL[DOC-MACRO purpose="Validate invite token and return safe preview data"]
|
|
188
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Authenticate invite tokens and track attempts"]
|
|
189
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
190
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Invite preview data" errors="Token invalid/expired/blocked" side_effects="Updates attempt count, blocks after max attempts"]
|
|
191
|
+
// CIL[DOC-MICRO logic_summary="Validates token, checks expiry/blocked status, increments attempts, blocks after max" notable_decisions="[]"]
|
|
192
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="complex_dependencies"]
|
|
193
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
194
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
195
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
196
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
197
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
198
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
199
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
149
200
|
export const validateToken = createHandler(async (_ctx, _body) => {
|
|
150
201
|
const token = _ctx.query?.token as string | undefined
|
|
151
202
|
if (!token) throw new Error('Token is required')
|
|
@@ -163,17 +214,14 @@ export const validateToken = createHandler(async (_ctx, _body) => {
|
|
|
163
214
|
|
|
164
215
|
const meta = person.data || {}
|
|
165
216
|
|
|
166
|
-
// Check blocked
|
|
167
217
|
if (meta.invite_blocked_at) {
|
|
168
218
|
throw new Error('This invite link has been blocked. Please contact the administrator.')
|
|
169
219
|
}
|
|
170
220
|
|
|
171
|
-
// Check expiry
|
|
172
221
|
if (meta.invite_expires_at && new Date(meta.invite_expires_at) < new Date()) {
|
|
173
222
|
throw new Error('This invite link has expired. Please request a new one.')
|
|
174
223
|
}
|
|
175
224
|
|
|
176
|
-
// Increment attempts
|
|
177
225
|
const attempts = (meta.invite_attempts || 0) + 1
|
|
178
226
|
const blocked = attempts >= MAX_ATTEMPTS
|
|
179
227
|
|
|
@@ -204,16 +252,23 @@ export const validateToken = createHandler(async (_ctx, _body) => {
|
|
|
204
252
|
kind: meta.invite_kind,
|
|
205
253
|
}
|
|
206
254
|
})
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
255
|
+
// CIL[END chunk_id="invites_validatetoken_1783968140"]
|
|
256
|
+
// CIL[BEGIN chunk_id="invites_completeregistration_1783968140" type="function" identity="completeRegistration" parent="none"]
|
|
257
|
+
// CIL[DOC-LEARNING topic="runtime-type:person/person" content="completeRegistration creates people records stamped with the person type's design_schema: full_name(text,required,system), email(email,required,system), phone(phone,system), status(text,required,system), is_active(boolean,required,system), avatar_url(url,system), created_at/updated_at(datetime,readonly,system). record_permissions allows member/member_admin/support/support_admin create/read/update and system_admin delete."]
|
|
258
|
+
// CIL[DOC-NOTE type="design_schema_caveat" content="The person record must have an account_id and role. The person type uses system=true for all fields, so values live on the people row, not in person.data."]
|
|
259
|
+
// CIL[DOC-MACRO purpose="Complete user registration after Supabase auth with multiple paths"]
|
|
260
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Handle walk-up registration flows and account assignment"]
|
|
261
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
262
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Registration result" errors="Invalid path, missing params, DB errors" side_effects="DB updates, role resolution"]
|
|
263
|
+
// CIL[DOC-MICRO logic_summary="Handles new_account, existing_account, and join_request paths with role resolution" notable_decisions="[]"]
|
|
264
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="complex_dependencies"]
|
|
265
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
266
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
267
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
268
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
269
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
270
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
271
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
217
272
|
export const completeRegistration = createHandler(async (ctx, body) => {
|
|
218
273
|
const { path, account_name, email, role_slug, target_account } = body
|
|
219
274
|
|
|
@@ -224,7 +279,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
224
279
|
if (path === 'existing_account') {
|
|
225
280
|
if (!target_account) throw new Error('target_account is required for existing_account path')
|
|
226
281
|
|
|
227
|
-
// Resolve the target account
|
|
228
282
|
const { data: targetAccount } = await adminDb
|
|
229
283
|
.from('accounts')
|
|
230
284
|
.select('id, slug')
|
|
@@ -235,7 +289,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
235
289
|
throw new Error(`Target account '${target_account}' not found`)
|
|
236
290
|
}
|
|
237
291
|
|
|
238
|
-
// Resolve person type_id
|
|
239
292
|
const { data: personType } = await adminDb
|
|
240
293
|
.from('types')
|
|
241
294
|
.select('id, design_schema, validation_schema')
|
|
@@ -243,7 +296,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
243
296
|
.eq('slug', 'person')
|
|
244
297
|
.single()
|
|
245
298
|
|
|
246
|
-
// Resolve role from role_slug param
|
|
247
299
|
const targetRoleSlug = role_slug || 'member'
|
|
248
300
|
|
|
249
301
|
let { data: roles } = await adminDb
|
|
@@ -254,7 +306,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
254
306
|
|
|
255
307
|
let role = roles?.[0]
|
|
256
308
|
|
|
257
|
-
// If specific role not found, try member_admin or member as fallback
|
|
258
309
|
if (!role) {
|
|
259
310
|
const { data: fallbackRoles } = await adminDb
|
|
260
311
|
.from('roles')
|
|
@@ -265,7 +316,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
265
316
|
role = fallbackRoles?.[0]
|
|
266
317
|
}
|
|
267
318
|
|
|
268
|
-
// Update the walk-up shell to be an active user on the target account
|
|
269
319
|
const { error: updateErr } = await adminDb
|
|
270
320
|
.from('people')
|
|
271
321
|
.update({
|
|
@@ -291,7 +341,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
291
341
|
if (path === 'new_account') {
|
|
292
342
|
if (!account_name) throw new Error('account_name is required for new_account path')
|
|
293
343
|
|
|
294
|
-
// Resolve person type_id
|
|
295
344
|
const { data: personType } = await adminDb
|
|
296
345
|
.from('types')
|
|
297
346
|
.select('id, design_schema, validation_schema')
|
|
@@ -299,7 +348,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
299
348
|
.eq('slug', 'person')
|
|
300
349
|
.single()
|
|
301
350
|
|
|
302
|
-
// Resolve account type_id
|
|
303
351
|
const { data: accountType } = await adminDb
|
|
304
352
|
.from('types')
|
|
305
353
|
.select('id')
|
|
@@ -307,7 +355,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
307
355
|
.eq('slug', 'account')
|
|
308
356
|
.single()
|
|
309
357
|
|
|
310
|
-
// Resolve role from role_slug param, or fall back to member_admin/member lookup
|
|
311
358
|
const targetRoleSlug = role_slug || 'member'
|
|
312
359
|
|
|
313
360
|
let { data: roles } = await adminDb
|
|
@@ -318,7 +365,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
318
365
|
|
|
319
366
|
let role = roles?.[0]
|
|
320
367
|
|
|
321
|
-
// If specific role not found, try member_admin or member as fallback
|
|
322
368
|
if (!role) {
|
|
323
369
|
const { data: fallbackRoles } = await adminDb
|
|
324
370
|
.from('roles')
|
|
@@ -329,8 +375,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
329
375
|
role = fallbackRoles?.[0]
|
|
330
376
|
}
|
|
331
377
|
|
|
332
|
-
// If no roles exist yet and no specific role was requested, create a default member role
|
|
333
|
-
// But only if we're not trying to assign a specific app role
|
|
334
378
|
if (!role && !role_slug) {
|
|
335
379
|
const { data: newRole, error: roleErr } = await adminDb
|
|
336
380
|
.from('roles')
|
|
@@ -349,7 +393,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
349
393
|
}
|
|
350
394
|
}
|
|
351
395
|
|
|
352
|
-
// Create account
|
|
353
396
|
const slug = generateAccountSlug(account_name)
|
|
354
397
|
const { data: account, error: accErr } = await adminDb
|
|
355
398
|
.from('accounts')
|
|
@@ -364,7 +407,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
364
407
|
|
|
365
408
|
if (accErr || !account) throw new Error('Failed to create account: ' + accErr?.message)
|
|
366
409
|
|
|
367
|
-
// Update the walk-up person shell (created by trigger as minimal record)
|
|
368
410
|
const { error: pErr } = await adminDb
|
|
369
411
|
.from('people')
|
|
370
412
|
.update({
|
|
@@ -375,7 +417,7 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
375
417
|
})
|
|
376
418
|
.eq('email', email)
|
|
377
419
|
.eq('status', 'active')
|
|
378
|
-
.is('role_id', null)
|
|
420
|
+
.is('role_id', null)
|
|
379
421
|
|
|
380
422
|
if (pErr) throw new Error('Failed to update person: ' + pErr.message)
|
|
381
423
|
|
|
@@ -386,7 +428,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
386
428
|
const { target_account_id, role_slug } = body
|
|
387
429
|
if (!target_account_id) throw new Error('target_account_id is required for join_request')
|
|
388
430
|
|
|
389
|
-
// Resolve role from role_slug param, or fall back to member
|
|
390
431
|
const targetRoleSlug = role_slug || 'member'
|
|
391
432
|
let { data: roles } = await adminDb
|
|
392
433
|
.from('roles')
|
|
@@ -396,7 +437,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
396
437
|
|
|
397
438
|
let role = roles?.[0]
|
|
398
439
|
|
|
399
|
-
// Fallback to member if specific role not found
|
|
400
440
|
if (!role) {
|
|
401
441
|
const { data: fallbackRoles } = await adminDb
|
|
402
442
|
.from('roles')
|
|
@@ -406,7 +446,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
406
446
|
role = fallbackRoles?.[0]
|
|
407
447
|
}
|
|
408
448
|
|
|
409
|
-
// Resolve type_id
|
|
410
449
|
const { data: personType } = await adminDb
|
|
411
450
|
.from('types')
|
|
412
451
|
.select('id, design_schema, validation_schema')
|
|
@@ -414,7 +453,6 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
414
453
|
.eq('slug', 'person')
|
|
415
454
|
.single()
|
|
416
455
|
|
|
417
|
-
// Update the walk-up shell to be a pending access request on the target account
|
|
418
456
|
const { error: pErr } = await adminDb
|
|
419
457
|
.from('people')
|
|
420
458
|
.update({
|
|
@@ -439,21 +477,21 @@ export const completeRegistration = createHandler(async (ctx, body) => {
|
|
|
439
477
|
|
|
440
478
|
throw new Error('Unknown registration path: ' + path)
|
|
441
479
|
})
|
|
442
|
-
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
480
|
+
// CIL[END chunk_id="invites_completeregistration_1783968140"]
|
|
481
|
+
// CIL[BEGIN chunk_id="invites_createinvite_1783968140" type="function" identity="createInvite" parent="none"]
|
|
482
|
+
// CIL[DOC-MACRO purpose="Create invite stub with metadata and return shareable URL"]
|
|
483
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Generate new invite tokens and create person records"]
|
|
484
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
485
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="{id, token, invite_url, expires_at}" errors="Auth required, permission denied, DB errors" side_effects="DB insert, audit logging"]
|
|
486
|
+
// CIL[DOC-MICRO logic_summary="Validates admin permissions, creates person stub with invite metadata, generates token" notable_decisions="[]"]
|
|
487
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="complex_dependencies"]
|
|
488
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
489
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
490
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
491
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
492
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
493
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
494
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
457
495
|
export const createInvite = createHandler(async (ctx, body) => {
|
|
458
496
|
if (!ctx.principal || ctx.principal.id === 'anonymous') {
|
|
459
497
|
throw new Error('Authentication required')
|
|
@@ -464,7 +502,6 @@ export const createInvite = createHandler(async (ctx, body) => {
|
|
|
464
502
|
throw new Error('email, account_id, and role_slug are required')
|
|
465
503
|
}
|
|
466
504
|
|
|
467
|
-
// Verify caller is admin for this account
|
|
468
505
|
const { data: caller } = await adminDb
|
|
469
506
|
.from('people')
|
|
470
507
|
.select('role:roles(slug)')
|
|
@@ -483,12 +520,10 @@ export const createInvite = createHandler(async (ctx, body) => {
|
|
|
483
520
|
throw new Error('Insufficient permissions — *_admin role required to create invites')
|
|
484
521
|
}
|
|
485
522
|
|
|
486
|
-
// Enforce allowed_role_slugs whitelist if provided
|
|
487
523
|
if (allowed_role_slugs && Array.isArray(allowed_role_slugs) && !allowed_role_slugs.includes(role_slug)) {
|
|
488
524
|
throw new Error(`Role '${role_slug}' is not permitted in this context`)
|
|
489
525
|
}
|
|
490
526
|
|
|
491
|
-
// Resolve role_id from slug
|
|
492
527
|
const { data: roleRows } = await adminDb
|
|
493
528
|
.from('roles')
|
|
494
529
|
.select('id, slug')
|
|
@@ -498,7 +533,6 @@ export const createInvite = createHandler(async (ctx, body) => {
|
|
|
498
533
|
const resolvedRole = roleRows?.[0]
|
|
499
534
|
if (!resolvedRole) throw new Error(`Role '${role_slug}' not found`)
|
|
500
535
|
|
|
501
|
-
// Check for existing unclaimed invite for this email+account
|
|
502
536
|
const { data: existing } = await adminDb
|
|
503
537
|
.from('people')
|
|
504
538
|
.select('id')
|
|
@@ -511,7 +545,6 @@ export const createInvite = createHandler(async (ctx, body) => {
|
|
|
511
545
|
throw new Error('A person with this email already exists or has a pending invite for this account')
|
|
512
546
|
}
|
|
513
547
|
|
|
514
|
-
// Resolve type_id for person
|
|
515
548
|
const { data: personType } = await adminDb
|
|
516
549
|
.from('types')
|
|
517
550
|
.select('id, design_schema, validation_schema')
|
|
@@ -523,31 +556,27 @@ export const createInvite = createHandler(async (ctx, body) => {
|
|
|
523
556
|
const expiresAt = new Date(Date.now() + INVITE_EXPIRY_DAYS * 86400 * 1000).toISOString()
|
|
524
557
|
const stubName = (full_name && full_name.trim()) ? full_name.trim() : email.split('@')[0]
|
|
525
558
|
|
|
526
|
-
const {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
},
|
|
546
|
-
})
|
|
547
|
-
.select('id')
|
|
548
|
-
.single()
|
|
559
|
+
const adminCtx = { ...ctx, query: { ...ctx.query, entity: 'people' } }
|
|
560
|
+
const stub = await adminDataCreate(adminCtx, {
|
|
561
|
+
entity: 'people',
|
|
562
|
+
type_id: personType?.id,
|
|
563
|
+
account_id,
|
|
564
|
+
full_name: stubName,
|
|
565
|
+
email,
|
|
566
|
+
role_id: resolvedRole.id,
|
|
567
|
+
status: 'invited',
|
|
568
|
+
is_active: true,
|
|
569
|
+
data: {
|
|
570
|
+
invite_token: token,
|
|
571
|
+
invite_kind: 'invite',
|
|
572
|
+
invite_expires_at: expiresAt,
|
|
573
|
+
invite_attempts: 0,
|
|
574
|
+
invite_blocked_at: null,
|
|
575
|
+
invite_invited_by: ctx.principal.id,
|
|
576
|
+
},
|
|
577
|
+
})
|
|
549
578
|
|
|
550
|
-
if (
|
|
579
|
+
if (!stub) throw new Error('Failed to create invite')
|
|
551
580
|
|
|
552
581
|
const appBaseUrl = base_url || process.env.APP_BASE_URL || 'https://yourapp.com'
|
|
553
582
|
const inviteUrl = `${appBaseUrl}/register?t=${token}`
|
|
@@ -558,17 +587,21 @@ export const createInvite = createHandler(async (ctx, body) => {
|
|
|
558
587
|
|
|
559
588
|
return { id: stub.id, token, invite_url: inviteUrl, expires_at: expiresAt }
|
|
560
589
|
})
|
|
561
|
-
|
|
562
|
-
//
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
590
|
+
// CIL[END chunk_id="invites_createinvite_1783968140"]
|
|
591
|
+
// CIL[BEGIN chunk_id="invites_listinvites_1783968140" type="function" identity="listInvites" parent="none"]
|
|
592
|
+
// CIL[DOC-MACRO purpose="List invite records with filtering and role-based access control"]
|
|
593
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Query and return scoped invite data"]
|
|
594
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
595
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Array of invite records" errors="Auth required, DB errors" side_effects="DB read operations"]
|
|
596
|
+
// CIL[DOC-MICRO logic_summary="Filters by account scope, role slugs, returns mapped invite data" notable_decisions="[]"]
|
|
597
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="complex_dependencies"]
|
|
598
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
599
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
600
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
601
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
602
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
603
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
604
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
572
605
|
export const listInvites = createHandler(async (ctx, _body) => {
|
|
573
606
|
if (!ctx.principal || ctx.principal.id === 'anonymous') {
|
|
574
607
|
throw new Error('Authentication required')
|
|
@@ -577,12 +610,10 @@ export const listInvites = createHandler(async (ctx, _body) => {
|
|
|
577
610
|
const isSystemAdmin = ctx.principal.permissions?.includes('system')
|
|
578
611
|
const { account_id: queryAccountId, role_slugs: queryRoleSlugs } = ctx.query || {}
|
|
579
612
|
|
|
580
|
-
// Parse optional role slug filter (comma-separated string from query param)
|
|
581
613
|
const roleSlugsFilter: string[] | null = queryRoleSlugs
|
|
582
614
|
? (queryRoleSlugs as string).split(',').map((s: string) => s.trim()).filter(Boolean)
|
|
583
615
|
: null
|
|
584
616
|
|
|
585
|
-
// If role slug filter is provided, resolve to role IDs first
|
|
586
617
|
let roleIdFilter: string[] | null = null
|
|
587
618
|
if (roleSlugsFilter && roleSlugsFilter.length > 0) {
|
|
588
619
|
const { data: roleRows } = await adminDb
|
|
@@ -592,7 +623,6 @@ export const listInvites = createHandler(async (ctx, _body) => {
|
|
|
592
623
|
|
|
593
624
|
roleIdFilter = (roleRows || []).map(r => r.id)
|
|
594
625
|
if (roleIdFilter.length === 0) {
|
|
595
|
-
// No matching roles — return empty list rather than unfiltered
|
|
596
626
|
return []
|
|
597
627
|
}
|
|
598
628
|
}
|
|
@@ -610,7 +640,6 @@ export const listInvites = createHandler(async (ctx, _body) => {
|
|
|
610
640
|
.order('created_at', { ascending: false })
|
|
611
641
|
|
|
612
642
|
if (!isSystemAdmin) {
|
|
613
|
-
// Scoped to caller's account
|
|
614
643
|
const scopedAccountId = queryAccountId || ctx.accountId
|
|
615
644
|
if (!scopedAccountId) throw new Error('Account scope required')
|
|
616
645
|
query = query.eq('account_id', scopedAccountId)
|
|
@@ -638,9 +667,21 @@ export const listInvites = createHandler(async (ctx, _body) => {
|
|
|
638
667
|
role: Array.isArray(row.role) ? row.role[0] : row.role,
|
|
639
668
|
}))
|
|
640
669
|
})
|
|
641
|
-
|
|
642
|
-
//
|
|
643
|
-
|
|
670
|
+
// CIL[END chunk_id="invites_listinvites_1783968140"]
|
|
671
|
+
// CIL[BEGIN chunk_id="invites_revokeinvite_1783968140" type="function" identity="revokeInvite" parent="none"]
|
|
672
|
+
// CIL[DOC-MACRO purpose="Soft-delete invite by setting status to inactive"]
|
|
673
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Revoke pending or invited person records"]
|
|
674
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
675
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="{success: boolean}" errors="Auth required, person not found, DB errors" side_effects="DB update, audit logging"]
|
|
676
|
+
// CIL[DOC-MICRO logic_summary="Sets person status to inactive, clears invite token, logs revocation" notable_decisions="[]"]
|
|
677
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
678
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
679
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
680
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
681
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
682
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
683
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
684
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
644
685
|
export const revokeInvite = createHandler(async (ctx, body) => {
|
|
645
686
|
if (!ctx.principal || ctx.principal.id === 'anonymous') throw new Error('Authentication required')
|
|
646
687
|
|
|
@@ -672,9 +713,21 @@ export const revokeInvite = createHandler(async (ctx, body) => {
|
|
|
672
713
|
|
|
673
714
|
return { success: true }
|
|
674
715
|
})
|
|
675
|
-
|
|
676
|
-
//
|
|
677
|
-
|
|
716
|
+
// CIL[END chunk_id="invites_revokeinvite_1783968140"]
|
|
717
|
+
// CIL[BEGIN chunk_id="invites_reinvite_1783968140" type="function" identity="reinvite" parent="none"]
|
|
718
|
+
// CIL[DOC-MACRO purpose="Issue new token for existing invite record"]
|
|
719
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Regenerate invite tokens for expired invites"]
|
|
720
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
721
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="{token, invite_url, expires_at}" errors="Auth required, person not found, DB errors" side_effects="DB update, audit logging"]
|
|
722
|
+
// CIL[DOC-MICRO logic_summary="Generates new token, resets attempts, updates expiry, returns new URL" notable_decisions="[]"]
|
|
723
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
724
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
725
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
726
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
727
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
728
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
729
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
730
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
678
731
|
export const reinvite = createHandler(async (ctx, body) => {
|
|
679
732
|
if (!ctx.principal || ctx.principal.id === 'anonymous') throw new Error('Authentication required')
|
|
680
733
|
|
|
@@ -721,9 +774,21 @@ export const reinvite = createHandler(async (ctx, body) => {
|
|
|
721
774
|
|
|
722
775
|
return { token: newToken, invite_url: inviteUrl, expires_at: expiresAt }
|
|
723
776
|
})
|
|
724
|
-
|
|
725
|
-
//
|
|
726
|
-
|
|
777
|
+
// CIL[END chunk_id="invites_reinvite_1783968140"]
|
|
778
|
+
// CIL[BEGIN chunk_id="invites_approve_1783968140" type="function" identity="approve" parent="none"]
|
|
779
|
+
// CIL[DOC-MACRO purpose="Approve pending access request and activate person"]
|
|
780
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Convert pending access requests to active users"]
|
|
781
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
782
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="{success: boolean}" errors="Auth required, request not found, DB errors" side_effects="DB update, audit logging"]
|
|
783
|
+
// CIL[DOC-MICRO logic_summary="Sets status to active, strips invite metadata, logs approval" notable_decisions="[]"]
|
|
784
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
785
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
786
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
787
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
788
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
789
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
790
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
791
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
727
792
|
export const approve = createHandler(async (ctx, body) => {
|
|
728
793
|
if (!ctx.principal || ctx.principal.id === 'anonymous') throw new Error('Authentication required')
|
|
729
794
|
|
|
@@ -739,7 +804,6 @@ export const approve = createHandler(async (ctx, body) => {
|
|
|
739
804
|
|
|
740
805
|
if (!person) throw new Error('Access request not found or already processed')
|
|
741
806
|
|
|
742
|
-
// Strip invite metadata on approval
|
|
743
807
|
const { invite_token, invite_kind, invite_expires_at, invite_attempts, invite_blocked_at, invite_invited_by, ...remainingData } =
|
|
744
808
|
(person.data as any) || {}
|
|
745
809
|
|
|
@@ -761,14 +825,21 @@ export const approve = createHandler(async (ctx, body) => {
|
|
|
761
825
|
|
|
762
826
|
return { success: true }
|
|
763
827
|
})
|
|
764
|
-
|
|
765
|
-
//
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
828
|
+
// CIL[END chunk_id="invites_approve_1783968140"]
|
|
829
|
+
// CIL[BEGIN chunk_id="invites_blockinvite_1783968140" type="function" identity="blockInvite" parent="none"]
|
|
830
|
+
// CIL[DOC-MACRO purpose="Manually block an invite token from being used"]
|
|
831
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Flag suspicious or failed invite attempts"]
|
|
832
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
833
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="{success: boolean}" errors="Auth required, person not found, DB errors" side_effects="DB update, audit logging"]
|
|
834
|
+
// CIL[DOC-MICRO logic_summary="Sets invite_blocked_at timestamp to prevent token validation" notable_decisions="[]"]
|
|
835
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
836
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
837
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
838
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
839
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
840
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
841
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
842
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
772
843
|
export const blockInvite = createHandler(async (ctx, body) => {
|
|
773
844
|
if (!ctx.principal || ctx.principal.id === 'anonymous') throw new Error('Authentication required')
|
|
774
845
|
|
|
@@ -804,9 +875,21 @@ export const blockInvite = createHandler(async (ctx, body) => {
|
|
|
804
875
|
|
|
805
876
|
return { success: true }
|
|
806
877
|
})
|
|
807
|
-
|
|
808
|
-
//
|
|
809
|
-
|
|
878
|
+
// CIL[END chunk_id="invites_blockinvite_1783968140"]
|
|
879
|
+
// CIL[BEGIN chunk_id="invites_deny_1783968140" type="function" identity="deny" parent="none"]
|
|
880
|
+
// CIL[DOC-MACRO purpose="Deny pending access request and deactivate person"]
|
|
881
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Reject access requests and mark users inactive"]
|
|
882
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
883
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="{success: boolean}" errors="Auth required, request not found, DB errors" side_effects="DB update, audit logging"]
|
|
884
|
+
// CIL[DOC-MICRO logic_summary="Sets status to inactive and is_active false, logs denial" notable_decisions="[]"]
|
|
885
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
886
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
887
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
888
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
889
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
890
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
891
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
892
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
810
893
|
export const deny = createHandler(async (ctx, body) => {
|
|
811
894
|
if (!ctx.principal || ctx.principal.id === 'anonymous') throw new Error('Authentication required')
|
|
812
895
|
|
|
@@ -839,19 +922,29 @@ export const deny = createHandler(async (ctx, body) => {
|
|
|
839
922
|
|
|
840
923
|
return { success: true }
|
|
841
924
|
})
|
|
842
|
-
|
|
843
|
-
//
|
|
844
|
-
|
|
925
|
+
// CIL[END chunk_id="invites_deny_1783968140"]
|
|
926
|
+
// CIL[BEGIN chunk_id="invites_handler_1783968140" type="function" identity="handler" parent="none"]
|
|
927
|
+
// CIL[DOC-MACRO purpose="Route invite actions to appropriate handler functions"]
|
|
928
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Dispatch invite API requests to correct handlers"]
|
|
929
|
+
// CIL[DOC-CONTEXT module="invites" layer="api-handler" related_files="[]"]
|
|
930
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="Varies by action" errors="Unknown action" side_effects="Delegates to handlers"]
|
|
931
|
+
// CIL[DOC-MICRO logic_summary="Switch statement routes action parameter to appropriate handler function" notable_decisions="[]"]
|
|
932
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
933
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
934
|
+
// CIL[DOC-VECTOR type="context" content="Layer: api-handler. Direct callers: 0. Direct calls: 0. Related files: none."]
|
|
935
|
+
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
936
|
+
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
937
|
+
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
938
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
939
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
845
940
|
export const handler = createPublicHandler(async (ctx, body) => {
|
|
846
941
|
const action = ctx.query?.action
|
|
847
942
|
|
|
848
943
|
switch (action) {
|
|
849
|
-
// Public endpoints (no auth required)
|
|
850
944
|
case 'check-first-user': return await checkFirstUser(ctx, body)
|
|
851
945
|
case 'check-domain': return await checkDomain(ctx, body)
|
|
852
946
|
case 'validate-token': return await validateToken(ctx, body)
|
|
853
947
|
case 'complete-registration': return await completeRegistration(ctx, body)
|
|
854
|
-
// Protected endpoints (require auth)
|
|
855
948
|
case 'create-invite': return await createInvite(ctx, body)
|
|
856
949
|
case 'list-invites': return await listInvites(ctx, body)
|
|
857
950
|
case 'revoke-invite': return await revokeInvite(ctx, body)
|
|
@@ -859,7 +952,114 @@ export const handler = createPublicHandler(async (ctx, body) => {
|
|
|
859
952
|
case 'block-invite': return await blockInvite(ctx, body)
|
|
860
953
|
case 'approve': return await approve(ctx, body)
|
|
861
954
|
case 'deny': return await deny(ctx, body)
|
|
955
|
+
case 'list-members': return await listMembers(ctx, body)
|
|
956
|
+
case 'update-member': return await updateMember(ctx, body)
|
|
957
|
+
case 'deactivate-member': return await deactivateMember(ctx, body)
|
|
862
958
|
default:
|
|
863
959
|
throw new Error(`Unknown action: ${action}`)
|
|
864
960
|
}
|
|
865
961
|
})
|
|
962
|
+
// CIL[END chunk_id="invites_handler_1783968140"]
|
|
963
|
+
|
|
964
|
+
// ─── Member management actions (runtime CRUD via admin-data) ─────────────────
|
|
965
|
+
|
|
966
|
+
function canManageMembers(ctx: any) {
|
|
967
|
+
if (!ctx.principal || ctx.principal.id === 'anonymous') return false
|
|
968
|
+
if (ctx.principal.permissions?.includes('system')) return true
|
|
969
|
+
return (ctx.principal.roles || []).some((r: string) => isAdminRole(r))
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
async function listMembers(ctx: any, body: any) {
|
|
973
|
+
if (!canManageMembers(ctx)) {
|
|
974
|
+
throw new Error('Insufficient permissions — *_admin role required to list members')
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const accountId = ctx.query?.account_id || body?.account_id || ctx.principal?.accountId
|
|
978
|
+
if (!accountId) throw new Error('account_id is required')
|
|
979
|
+
|
|
980
|
+
const adminCtx = { ...ctx, query: { ...ctx.query, entity: 'people', type_slug: 'person', account_id: accountId } }
|
|
981
|
+
const records = await adminDataList(adminCtx, {})
|
|
982
|
+
|
|
983
|
+
const roleIds = (records || [])
|
|
984
|
+
.map((r: any) => r.role_id)
|
|
985
|
+
.filter(Boolean)
|
|
986
|
+
const roleMap: Record<string, { slug: string; name: string }> = {}
|
|
987
|
+
if (roleIds.length) {
|
|
988
|
+
const { data: roles } = await adminDb
|
|
989
|
+
.from('roles')
|
|
990
|
+
.select('id, slug, name')
|
|
991
|
+
.in('id', roleIds)
|
|
992
|
+
for (const r of roles || []) {
|
|
993
|
+
roleMap[r.id] = { slug: r.slug, name: r.name }
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const data = (records || []).map((r: any) => ({
|
|
998
|
+
...r,
|
|
999
|
+
role: r.role_id ? roleMap[r.role_id] || { id: r.role_id } : null
|
|
1000
|
+
}))
|
|
1001
|
+
|
|
1002
|
+
return { data }
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
async function resolveRoleId(slug: string) {
|
|
1006
|
+
const { data: roleRows } = await adminDb
|
|
1007
|
+
.from('roles')
|
|
1008
|
+
.select('id, slug')
|
|
1009
|
+
.eq('slug', slug)
|
|
1010
|
+
.limit(1)
|
|
1011
|
+
const resolved = roleRows?.[0]
|
|
1012
|
+
if (!resolved) throw new Error(`Role '${slug}' not found`)
|
|
1013
|
+
return resolved.id
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
async function updateMember(ctx: any, body: any) {
|
|
1017
|
+
if (!canManageMembers(ctx)) {
|
|
1018
|
+
throw new Error('Insufficient permissions — *_admin role required to update members')
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const { person_id, full_name, role_slug } = body
|
|
1022
|
+
if (!person_id) throw new Error('person_id is required')
|
|
1023
|
+
|
|
1024
|
+
const recordData: any = {}
|
|
1025
|
+
if (full_name !== undefined) recordData.full_name = full_name
|
|
1026
|
+
if (role_slug !== undefined) recordData.role_id = await resolveRoleId(role_slug)
|
|
1027
|
+
|
|
1028
|
+
if (Object.keys(recordData).length === 0) {
|
|
1029
|
+
throw new Error('No update fields provided')
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
const adminCtx = {
|
|
1033
|
+
...ctx,
|
|
1034
|
+
query: { ...ctx.query, entity: 'people', id: person_id }
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const updated = await adminDataUpdate(adminCtx, recordData)
|
|
1038
|
+
if (!updated) throw new Error('Failed to update member')
|
|
1039
|
+
|
|
1040
|
+
await emitLog(ctx, 'member.updated', { type: 'person', id: person_id }, { after: recordData })
|
|
1041
|
+
return { success: true, data: updated }
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
async function deactivateMember(ctx: any, body: any) {
|
|
1045
|
+
if (!canManageMembers(ctx)) {
|
|
1046
|
+
throw new Error('Insufficient permissions — *_admin role required to deactivate members')
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const { person_id } = body
|
|
1050
|
+
if (!person_id) throw new Error('person_id is required')
|
|
1051
|
+
|
|
1052
|
+
const adminCtx = {
|
|
1053
|
+
...ctx,
|
|
1054
|
+
query: { ...ctx.query, entity: 'people', id: person_id }
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const updated = await adminDataUpdate(adminCtx, {
|
|
1058
|
+
status: 'inactive',
|
|
1059
|
+
is_active: false,
|
|
1060
|
+
})
|
|
1061
|
+
if (!updated) throw new Error('Failed to deactivate member')
|
|
1062
|
+
|
|
1063
|
+
await emitLog(ctx, 'member.deactivated', { type: 'person', id: person_id }, {})
|
|
1064
|
+
return { success: true, data: updated }
|
|
1065
|
+
}
|