spine-framework 0.1.0
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/README.md +129 -0
- package/.framework/cli/bin.cjs +14 -0
- package/.framework/cli/commands/agents.ts +153 -0
- package/.framework/cli/commands/auth.ts +94 -0
- package/.framework/cli/commands/create-app.ts +185 -0
- package/.framework/cli/commands/dev.ts +295 -0
- package/.framework/cli/commands/doctor.ts +442 -0
- package/.framework/cli/commands/generate.ts +332 -0
- package/.framework/cli/commands/init.ts +272 -0
- package/.framework/cli/commands/install-app.ts +391 -0
- package/.framework/cli/commands/items.ts +253 -0
- package/.framework/cli/commands/migrations.ts +141 -0
- package/.framework/cli/commands/pipelines.ts +166 -0
- package/.framework/cli/commands/status.ts +197 -0
- package/.framework/cli/commands/system.ts +184 -0
- package/.framework/cli/commands/test.ts +227 -0
- package/.framework/cli/commands/uninstall-app.ts +166 -0
- package/.framework/cli/context.ts +268 -0
- package/.framework/cli/env-loader.ts +36 -0
- package/.framework/cli/index.ts +106 -0
- package/.framework/cli/welcome.cjs +45 -0
- package/.framework/docs/API.md +384 -0
- package/.framework/docs/STABILITY.md +52 -0
- package/.framework/docs/admin-routes.md +76 -0
- package/.framework/docs/api-docs-progress.md +38 -0
- package/.framework/docs/api-governance.md +146 -0
- package/.framework/docs/api-testing-results.md +212 -0
- package/.framework/docs/apis/admin-configs.md +567 -0
- package/.framework/docs/apis/admin-data.md +272 -0
- package/.framework/docs/apis/index.md +231 -0
- package/.framework/docs/apis/internal.md +295 -0
- package/.framework/docs/apis/runtime.md +537 -0
- package/.framework/docs/assembly-launch-guide.md +138 -0
- package/.framework/docs/audit-results.md +590 -0
- package/.framework/docs/authorization-model.md +170 -0
- package/.framework/docs/db-api-inventory.md +95 -0
- package/.framework/docs/examples/custom-app/README.md +77 -0
- package/.framework/docs/examples/custom-function/README.md +27 -0
- package/.framework/docs/examples/custom-function/handler.ts +48 -0
- package/.framework/docs/examples/custom-webhook/README.md +68 -0
- package/.framework/docs/gap-remediation-backlog.md +103 -0
- package/.framework/docs/guides/cli-guide.md +224 -0
- package/.framework/docs/guides/getting-started.md +103 -0
- package/.framework/docs/guides/import-guide.md +193 -0
- package/.framework/docs/guides/testing-guide.md +229 -0
- package/.framework/docs/permission-examples.md +326 -0
- package/.framework/docs/ui-adoption-verification.md +111 -0
- package/.framework/docs/ui-api-coverage.md +84 -0
- package/.framework/docs/v2-compatibility-audit.md +228 -0
- package/.framework/functions/.gitkeep +1 -0
- package/.framework/functions/_shared/agent-runner.ts +1097 -0
- package/.framework/functions/_shared/app-manifest.ts +184 -0
- package/.framework/functions/_shared/audit.ts +150 -0
- package/.framework/functions/_shared/db.ts +174 -0
- package/.framework/functions/_shared/index.ts +382 -0
- package/.framework/functions/_shared/middleware.ts +490 -0
- package/.framework/functions/_shared/permissions.ts +1325 -0
- package/.framework/functions/_shared/pipeline-runner.ts +731 -0
- package/.framework/functions/_shared/principal.ts +760 -0
- package/.framework/functions/_shared/schema-utils.ts +967 -0
- package/.framework/functions/_shared/testing.ts +258 -0
- package/.framework/functions/_shared/trigger-engine.ts +425 -0
- package/.framework/functions/_shared/webhook-registration.ts +168 -0
- package/.framework/functions/_shared/webhook-registry.ts +129 -0
- package/.framework/functions/account-nodes.ts +111 -0
- package/.framework/functions/admin-data.ts +606 -0
- package/.framework/functions/ai-agents.ts +323 -0
- package/.framework/functions/api-keys.ts +376 -0
- package/.framework/functions/apps.ts +483 -0
- package/.framework/functions/auth.ts +196 -0
- package/.framework/functions/debug-auth.ts +107 -0
- package/.framework/functions/embeddings.ts +556 -0
- package/.framework/functions/integration-routes.ts +523 -0
- package/.framework/functions/integrations.ts +319 -0
- package/.framework/functions/item-progress.ts +272 -0
- package/.framework/functions/logs.ts +438 -0
- package/.framework/functions/observability.ts +275 -0
- package/.framework/functions/pipeline-executions.ts +494 -0
- package/.framework/functions/pipelines.ts +485 -0
- package/.framework/functions/prompt-configs.ts +339 -0
- package/.framework/functions/roles.ts +387 -0
- package/.framework/functions/system-cron.ts +742 -0
- package/.framework/functions/system.ts +323 -0
- package/.framework/functions/tests.ts +119 -0
- package/.framework/functions/timers.ts +357 -0
- package/.framework/functions/triggers.ts +563 -0
- package/.framework/functions/types.ts +604 -0
- package/.framework/migrations/000_foundation.sql +1256 -0
- package/.framework/migrations/001_seed.sql +92 -0
- package/.framework/migrations/002_seed_constraints.sql +13 -0
- package/.framework/migrations/003_auth_user_trigger.sql +59 -0
- package/.framework/src/App.tsx +126 -0
- package/.framework/src/apps/admin/index.tsx +173 -0
- package/.framework/src/components/AppWrapper.tsx +56 -0
- package/.framework/src/components/CustomAppLoader.tsx +116 -0
- package/.framework/src/components/admin/AdminListPage.tsx +151 -0
- package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
- package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
- package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
- package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
- package/.framework/src/components/app-sidebar.tsx +228 -0
- package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
- package/.framework/src/components/layout/AppShell.tsx +91 -0
- package/.framework/src/components/layout/Header.tsx +88 -0
- package/.framework/src/components/layout/Layout.tsx +95 -0
- package/.framework/src/components/layout/Sidebar.tsx +329 -0
- package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
- package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
- package/.framework/src/components/runtime/DataFilters.tsx +91 -0
- package/.framework/src/components/runtime/DataHeader.tsx +68 -0
- package/.framework/src/components/runtime/DataListPage.tsx +124 -0
- package/.framework/src/components/runtime/DataStats.tsx +70 -0
- package/.framework/src/components/runtime/DataTable.tsx +174 -0
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
- package/.framework/src/components/runtime/index.ts +18 -0
- package/.framework/src/components/search-form.tsx +29 -0
- package/.framework/src/components/shared/AgentView.tsx +213 -0
- package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
- package/.framework/src/components/shared/SchemaFields.tsx +226 -0
- package/.framework/src/components/ui/DataTable.tsx +343 -0
- package/.framework/src/components/ui/Form.tsx +281 -0
- package/.framework/src/components/ui/ItemCard.tsx +296 -0
- package/.framework/src/components/ui/ItemListView.tsx +308 -0
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
- package/.framework/src/components/ui/Modal.tsx +61 -0
- package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
- package/.framework/src/components/ui/accordion.tsx +82 -0
- package/.framework/src/components/ui/alert-dialog.tsx +197 -0
- package/.framework/src/components/ui/alert.tsx +76 -0
- package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
- package/.framework/src/components/ui/avatar.tsx +110 -0
- package/.framework/src/components/ui/badge.tsx +49 -0
- package/.framework/src/components/ui/breadcrumb.tsx +122 -0
- package/.framework/src/components/ui/button-group.tsx +83 -0
- package/.framework/src/components/ui/button.tsx +65 -0
- package/.framework/src/components/ui/calendar.tsx +222 -0
- package/.framework/src/components/ui/card.tsx +100 -0
- package/.framework/src/components/ui/carousel.tsx +240 -0
- package/.framework/src/components/ui/chart.tsx +373 -0
- package/.framework/src/components/ui/checkbox.tsx +31 -0
- package/.framework/src/components/ui/collapsible.tsx +33 -0
- package/.framework/src/components/ui/combobox.tsx +299 -0
- package/.framework/src/components/ui/command.tsx +193 -0
- package/.framework/src/components/ui/context-menu.tsx +261 -0
- package/.framework/src/components/ui/dialog.tsx +165 -0
- package/.framework/src/components/ui/direction.tsx +22 -0
- package/.framework/src/components/ui/drawer.tsx +132 -0
- package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
- package/.framework/src/components/ui/empty.tsx +104 -0
- package/.framework/src/components/ui/field.tsx +238 -0
- package/.framework/src/components/ui/hover-card.tsx +42 -0
- package/.framework/src/components/ui/input-group.tsx +153 -0
- package/.framework/src/components/ui/input-otp.tsx +87 -0
- package/.framework/src/components/ui/input.tsx +19 -0
- package/.framework/src/components/ui/item.tsx +196 -0
- package/.framework/src/components/ui/kbd.tsx +26 -0
- package/.framework/src/components/ui/label.tsx +22 -0
- package/.framework/src/components/ui/menubar.tsx +277 -0
- package/.framework/src/components/ui/native-select.tsx +61 -0
- package/.framework/src/components/ui/navigation-menu.tsx +164 -0
- package/.framework/src/components/ui/pagination.tsx +129 -0
- package/.framework/src/components/ui/popover.tsx +87 -0
- package/.framework/src/components/ui/progress.tsx +31 -0
- package/.framework/src/components/ui/radio-group.tsx +42 -0
- package/.framework/src/components/ui/resizable.tsx +50 -0
- package/.framework/src/components/ui/scroll-area.tsx +53 -0
- package/.framework/src/components/ui/select.tsx +195 -0
- package/.framework/src/components/ui/separator.tsx +26 -0
- package/.framework/src/components/ui/sheet.tsx +145 -0
- package/.framework/src/components/ui/sidebar.tsx +706 -0
- package/.framework/src/components/ui/skeleton.tsx +13 -0
- package/.framework/src/components/ui/slider.tsx +59 -0
- package/.framework/src/components/ui/sonner.tsx +47 -0
- package/.framework/src/components/ui/spinner.tsx +10 -0
- package/.framework/src/components/ui/switch.tsx +33 -0
- package/.framework/src/components/ui/table-primitives.tsx +141 -0
- package/.framework/src/components/ui/table.tsx +114 -0
- package/.framework/src/components/ui/tabs.tsx +90 -0
- package/.framework/src/components/ui/textarea.tsx +18 -0
- package/.framework/src/components/ui/toggle-group.tsx +89 -0
- package/.framework/src/components/ui/toggle.tsx +45 -0
- package/.framework/src/components/ui/tooltip.tsx +57 -0
- package/.framework/src/contexts/AppContext.tsx +133 -0
- package/.framework/src/contexts/AuthContext.tsx +371 -0
- package/.framework/src/hooks/use-mobile.ts +19 -0
- package/.framework/src/hooks/useApi.ts +526 -0
- package/.framework/src/hooks/useApps.ts +114 -0
- package/.framework/src/hooks/useEntityList.ts +190 -0
- package/.framework/src/hooks/useEntityRecord.ts +308 -0
- package/.framework/src/hooks/useForm.ts +307 -0
- package/.framework/src/hooks/useListSchema.ts +264 -0
- package/.framework/src/hooks/useSchemaRecord.ts +223 -0
- package/.framework/src/index.css +128 -0
- package/.framework/src/lib/api.ts +156 -0
- package/.framework/src/lib/supabase.ts +94 -0
- package/.framework/src/lib/utils.ts +317 -0
- package/.framework/src/main.tsx +27 -0
- package/.framework/src/pages/DashboardPage.tsx +181 -0
- package/.framework/src/pages/NotFoundPage.tsx +39 -0
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
- package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
- package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
- package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
- package/.framework/src/pages/admin/AppsPage.tsx +355 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
- package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
- package/.framework/src/pages/admin/LogsPage.tsx +283 -0
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
- package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
- package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
- package/.framework/src/pages/admin/RolesPage.tsx +292 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
- package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
- package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
- package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
- package/.framework/src/pages/admin/TimersPage.tsx +376 -0
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
- package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
- package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
- package/.framework/src/pages/admin/TypesPage.tsx +295 -0
- package/.framework/src/pages/auth/LoginPage.tsx +188 -0
- package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
- package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
- package/.framework/src/types/auth.ts +125 -0
- package/.framework/src/types/types.ts +407 -0
- package/STRUCTURE.md +150 -0
- package/config/components.json +25 -0
- package/config/deno.lock +108 -0
- package/config/package-lock.json +17183 -0
- package/config/postcss.config.cjs +10 -0
- package/config/tailwind.config.cjs +78 -0
- package/config/tsconfig.build.json +32 -0
- package/config/tsconfig.cli.json +18 -0
- package/config/tsconfig.json +41 -0
- package/config/tsconfig.node.json +17 -0
- package/config/tsconfig.node.tsbuildinfo +1 -0
- package/config/tsconfig.tsbuildinfo +1 -0
- package/config/typedoc.json +16 -0
- package/config/vite.config.d.ts +2 -0
- package/config/vite.config.ts +72 -0
- package/dist/cli/commands/agents.d.ts +39 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/auth.d.ts +36 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/create-app.d.ts +23 -0
- package/dist/cli/commands/create-app.d.ts.map +1 -0
- package/dist/cli/commands/dev.d.ts +39 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +42 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +36 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +30 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/install-app.d.ts +30 -0
- package/dist/cli/commands/install-app.d.ts.map +1 -0
- package/dist/cli/commands/items.d.ts +45 -0
- package/dist/cli/commands/items.d.ts.map +1 -0
- package/dist/cli/commands/migrations.d.ts +41 -0
- package/dist/cli/commands/migrations.d.ts.map +1 -0
- package/dist/cli/commands/pipelines.d.ts +40 -0
- package/dist/cli/commands/pipelines.d.ts.map +1 -0
- package/dist/cli/commands/status.d.ts +23 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/system.d.ts +29 -0
- package/dist/cli/commands/system.d.ts.map +1 -0
- package/dist/cli/commands/test.d.ts +46 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/uninstall-app.d.ts +23 -0
- package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
- package/dist/cli/context.d.ts +88 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/env-loader.d.ts +14 -0
- package/dist/cli/env-loader.d.ts.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/functions/_shared/agent-runner.d.ts +156 -0
- package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
- package/dist/functions/_shared/app-manifest.d.ts +68 -0
- package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
- package/dist/functions/_shared/audit.d.ts +91 -0
- package/dist/functions/_shared/audit.d.ts.map +1 -0
- package/dist/functions/_shared/db.d.ts +125 -0
- package/dist/functions/_shared/db.d.ts.map +1 -0
- package/dist/functions/_shared/index.d.ts +298 -0
- package/dist/functions/_shared/index.d.ts.map +1 -0
- package/dist/functions/_shared/middleware.d.ts +315 -0
- package/dist/functions/_shared/middleware.d.ts.map +1 -0
- package/dist/functions/_shared/permissions.d.ts +626 -0
- package/dist/functions/_shared/permissions.d.ts.map +1 -0
- package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
- package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
- package/dist/functions/_shared/principal.d.ts +284 -0
- package/dist/functions/_shared/principal.d.ts.map +1 -0
- package/dist/functions/_shared/schema-utils.d.ts +181 -0
- package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
- package/dist/functions/_shared/testing.d.ts +172 -0
- package/dist/functions/_shared/testing.d.ts.map +1 -0
- package/dist/functions/_shared/trigger-engine.d.ts +140 -0
- package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registration.d.ts +81 -0
- package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registry.d.ts +57 -0
- package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
- package/dist/functions/account-nodes.d.ts +48 -0
- package/dist/functions/account-nodes.d.ts.map +1 -0
- package/dist/functions/admin-data.d.ts +178 -0
- package/dist/functions/admin-data.d.ts.map +1 -0
- package/dist/functions/ai-agents.d.ts +125 -0
- package/dist/functions/ai-agents.d.ts.map +1 -0
- package/dist/functions/api-keys.d.ts +140 -0
- package/dist/functions/api-keys.d.ts.map +1 -0
- package/dist/functions/apps.d.ts +163 -0
- package/dist/functions/apps.d.ts.map +1 -0
- package/dist/functions/auth.d.ts +74 -0
- package/dist/functions/auth.d.ts.map +1 -0
- package/dist/functions/debug-auth.d.ts +33 -0
- package/dist/functions/debug-auth.d.ts.map +1 -0
- package/dist/functions/embeddings.d.ts +205 -0
- package/dist/functions/embeddings.d.ts.map +1 -0
- package/dist/functions/integration-routes.d.ts +45 -0
- package/dist/functions/integration-routes.d.ts.map +1 -0
- package/dist/functions/integrations.d.ts +124 -0
- package/dist/functions/integrations.d.ts.map +1 -0
- package/dist/functions/item-progress.d.ts +41 -0
- package/dist/functions/item-progress.d.ts.map +1 -0
- package/dist/functions/logs.d.ts +162 -0
- package/dist/functions/logs.d.ts.map +1 -0
- package/dist/functions/observability.d.ts +123 -0
- package/dist/functions/observability.d.ts.map +1 -0
- package/dist/functions/pipeline-executions.d.ts +190 -0
- package/dist/functions/pipeline-executions.d.ts.map +1 -0
- package/dist/functions/pipelines.d.ts +171 -0
- package/dist/functions/pipelines.d.ts.map +1 -0
- package/dist/functions/prompt-configs.d.ts +125 -0
- package/dist/functions/prompt-configs.d.ts.map +1 -0
- package/dist/functions/roles.d.ts +118 -0
- package/dist/functions/roles.d.ts.map +1 -0
- package/dist/functions/system-cron.d.ts +65 -0
- package/dist/functions/system-cron.d.ts.map +1 -0
- package/dist/functions/system.d.ts +29 -0
- package/dist/functions/system.d.ts.map +1 -0
- package/dist/functions/tests.d.ts +28 -0
- package/dist/functions/tests.d.ts.map +1 -0
- package/dist/functions/timers.d.ts +139 -0
- package/dist/functions/timers.d.ts.map +1 -0
- package/dist/functions/triggers.d.ts +203 -0
- package/dist/functions/triggers.d.ts.map +1 -0
- package/dist/functions/types.d.ts +151 -0
- package/dist/functions/types.d.ts.map +1 -0
- package/dist/src/types/types.d.ts +364 -0
- package/dist/src/types/types.d.ts.map +1 -0
- package/package.json +192 -0
- package/scripts/app-install-cli.ts +286 -0
- package/scripts/assemble-frontend.sh +79 -0
- package/scripts/assemble-functions.sh +62 -0
- package/scripts/assemble.sh +35 -0
- package/scripts/boundary-check.sh +106 -0
- package/scripts/build-manifest.sh +80 -0
- package/scripts/check-core-integrity.sh +82 -0
- package/scripts/ingest-chunks.cjs +202 -0
- package/scripts/kb-chunk-parser.cjs +312 -0
- package/scripts/kb-chunk-parser.ts +330 -0
- package/scripts/load-test-app-install.ts +484 -0
- package/scripts/netlify-dev-wrapper.sh +22 -0
- package/scripts/verify-integrity.sh +69 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module apps
|
|
3
|
+
* @audience core-contributor
|
|
4
|
+
* @layer api-handler
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* CRUD API for the `apps` table. Apps are the installable units of functionality
|
|
8
|
+
* in Spine v2. They group types, roles, integrations, and nav configuration.
|
|
9
|
+
*
|
|
10
|
+
* **Routed by:** `GET/POST/PATCH/DELETE /.netlify/functions/apps`
|
|
11
|
+
*
|
|
12
|
+
* **Authorization model:**
|
|
13
|
+
* - `list` / `get` / `getSchema` / `checkAvailability`: any authenticated principal
|
|
14
|
+
* (RLS-scoped via `ctx.db`). Returns sanitized records.
|
|
15
|
+
* - `create`: requires `isSystemAdmin` or first-surface `canCreate` permission.
|
|
16
|
+
* - `update` / `remove`: requires authenticated principal + field-level permission
|
|
17
|
+
* check via `validateUpdatePermissions`.
|
|
18
|
+
* - `updateVersion`: authenticated principal via RPC.
|
|
19
|
+
*
|
|
20
|
+
* INVARIANT: app slugs are globally unique across the `apps` table.
|
|
21
|
+
* INVARIANT: `is_system` apps can only be manipulated by system admins.
|
|
22
|
+
*
|
|
23
|
+
* @seeAlso middleware.ts (createHandler)
|
|
24
|
+
* @seeAlso permissions.ts (PermissionEngine, sanitizeRecordData)
|
|
25
|
+
* @seeAlso audit.ts (emitLog for app.created / app.updated / app.deleted)
|
|
26
|
+
* @seeAlso types.ts (types reference apps via app_id)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { createHandler } from './_shared/middleware'
|
|
30
|
+
import { joins } from './_shared/db'
|
|
31
|
+
import { emitLog } from './_shared/audit'
|
|
32
|
+
import { PermissionEngine, sanitizeRecordData } from './_shared/permissions'
|
|
33
|
+
|
|
34
|
+
const permissions = PermissionEngine as any
|
|
35
|
+
|
|
36
|
+
// ─── HANDLERS ─────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
// ─── CHUNK_START: APPS_LIST ──────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* @chunk-id APPS_LIST_1_0_0
|
|
41
|
+
* @version 1.0.0
|
|
42
|
+
* @hash 8eaac3c01786fadeb3c4acb34c9725378f2055766213493859835cadf2bb3963
|
|
43
|
+
* @macro Apps List Handler
|
|
44
|
+
* @micro Lists accessible apps via RPC with filtering and sanitization
|
|
45
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
46
|
+
* @inputs body: any — Request body (unused for GET)
|
|
47
|
+
* @outputs Array of sanitized app records
|
|
48
|
+
* @depends-on [createHandler, sanitizeRecordData]
|
|
49
|
+
* @depended-by [Netlify function routing]
|
|
50
|
+
* @side-effects [RPC call, permission sanitization]
|
|
51
|
+
* @tags apps, list, crud, rpc
|
|
52
|
+
*/
|
|
53
|
+
export const list = createHandler(async (ctx, body) => {
|
|
54
|
+
const { include_system, include_inactive, account_id } = ctx.query || {}
|
|
55
|
+
|
|
56
|
+
const targetAccountId = account_id || ctx.accountId
|
|
57
|
+
|
|
58
|
+
if (!targetAccountId) {
|
|
59
|
+
throw new Error('Account context required')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// RLS automatically filters to accessible accounts
|
|
63
|
+
const { data, error: err } = await ctx.db
|
|
64
|
+
.rpc('get_account_apps', {
|
|
65
|
+
account_id: targetAccountId,
|
|
66
|
+
include_system: include_system !== 'false',
|
|
67
|
+
include_inactive: include_inactive === 'true'
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (err) throw err
|
|
71
|
+
|
|
72
|
+
// Sanitize each record based on role permissions
|
|
73
|
+
const sanitized = []
|
|
74
|
+
for (const app of data || []) {
|
|
75
|
+
sanitized.push(await sanitizeRecordData(ctx, app, 'app'))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return sanitized
|
|
79
|
+
})
|
|
80
|
+
// ─── CHUNK_END: APPS_LIST ────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
// ─── CHUNK_START: APPS_GET ──────────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* @chunk-id APPS_GET_1_0_0
|
|
85
|
+
* @version 1.0.0
|
|
86
|
+
* @hash 5449bb3ac8726775412df541e1f2abf2400fb18751e3c9aad5f0aafb09cc7f60
|
|
87
|
+
* @macro App Get Handler
|
|
88
|
+
* @micro Returns single app by ID or slug with owner account join
|
|
89
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
90
|
+
* @inputs body: any — Request body (unused for GET)
|
|
91
|
+
* @outputs Sanitized app record with ownerAccount join
|
|
92
|
+
* @depends-on [createHandler, joins, sanitizeRecordData]
|
|
93
|
+
* @depended-by [Netlify function routing]
|
|
94
|
+
* @side-effects [DB single row query, permission sanitization]
|
|
95
|
+
* @tags apps, get, crud, single-record
|
|
96
|
+
*/
|
|
97
|
+
export const get = createHandler(async (ctx, body) => {
|
|
98
|
+
const { id, slug } = ctx.query || {}
|
|
99
|
+
|
|
100
|
+
if (!id && !slug) {
|
|
101
|
+
throw new Error('App ID or slug is required')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let query = ctx.db
|
|
105
|
+
.from('apps')
|
|
106
|
+
.select(`*, ${joins.ownerAccount}`)
|
|
107
|
+
.eq('is_active', true)
|
|
108
|
+
|
|
109
|
+
if (id) {
|
|
110
|
+
query = query.eq('id', id)
|
|
111
|
+
} else {
|
|
112
|
+
query = query.eq('slug', slug)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const { data, error: err } = await query.single()
|
|
116
|
+
|
|
117
|
+
if (err) throw err
|
|
118
|
+
|
|
119
|
+
// Sanitize based on role permissions
|
|
120
|
+
return await sanitizeRecordData(ctx, data, 'app')
|
|
121
|
+
})
|
|
122
|
+
// ─── CHUNK_END: APPS_GET ────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
// ─── CHUNK_START: APPS_GET_SCHEMA ──────────────────────────────────────────────
|
|
125
|
+
/**
|
|
126
|
+
* @chunk-id APPS_GET_SCHEMA_1_0_0
|
|
127
|
+
* @version 1.0.0
|
|
128
|
+
* @hash 8b6b7bb419114cbb699ad841dc4ba760ff36a626e8a9513de24715229e5a064b
|
|
129
|
+
* @macro App Schema Handler
|
|
130
|
+
* @micro Returns full app schema via RPC with types, roles, views, integrations
|
|
131
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
132
|
+
* @inputs body: any — Request body (unused for GET)
|
|
133
|
+
* @outputs App schema object with complete configuration
|
|
134
|
+
* @depends-on [createHandler]
|
|
135
|
+
* @depended-by [Netlify function routing]
|
|
136
|
+
* @side-effects [RPC call for schema retrieval]
|
|
137
|
+
* @tags apps, schema, rpc, configuration
|
|
138
|
+
*/
|
|
139
|
+
export const getSchema = createHandler(async (ctx, body) => {
|
|
140
|
+
const { slug } = ctx.query || {}
|
|
141
|
+
|
|
142
|
+
if (!slug) {
|
|
143
|
+
throw new Error('App slug is required')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const { data, error: err } = await ctx.db
|
|
147
|
+
.rpc('get_app_schema', { app_slug: slug })
|
|
148
|
+
|
|
149
|
+
if (err) throw err
|
|
150
|
+
|
|
151
|
+
return data
|
|
152
|
+
})
|
|
153
|
+
// ─── CHUNK_END: APPS_GET_SCHEMA ────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
// ─── CHUNK_START: APPS_CREATE ──────────────────────────────────────────────
|
|
156
|
+
/**
|
|
157
|
+
* @chunk-id APPS_CREATE_1_0_0
|
|
158
|
+
* @version 1.0.0
|
|
159
|
+
* @hash f440efbf4a020b2d0d21ed7d90c5995538dd76d631f1b16fc53c1f2e9a1e4f91
|
|
160
|
+
* @macro App Create Handler
|
|
161
|
+
* @micro Creates new app with permission validation and audit logging
|
|
162
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
163
|
+
* @inputs body: object — App configuration data including slug and name
|
|
164
|
+
* @outputs Inserted app record
|
|
165
|
+
* @depends-on [createHandler, permissions, emitLog]
|
|
166
|
+
* @depended-by [Netlify function routing]
|
|
167
|
+
* @side-effects [DB insert, permission checks, audit logging]
|
|
168
|
+
* @tags apps, create, crud, permissions, audit
|
|
169
|
+
*/
|
|
170
|
+
export const create = createHandler(async (ctx, body) => {
|
|
171
|
+
const { slug, name, description, icon, color, version, app_type, source, owner_account_id, config, nav_items, min_role, integration_deps, metadata, route_prefix, renderer } = body
|
|
172
|
+
|
|
173
|
+
if (!slug || !name) {
|
|
174
|
+
throw new Error('slug and name are required')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!ctx.principal || ctx.principal.id === 'anonymous' || !ctx.accountId) {
|
|
178
|
+
throw new Error('User context (person and account) required')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check create permissions
|
|
182
|
+
if (!permissions.isSystemAdmin(ctx)) {
|
|
183
|
+
const perms = await permissions.resolveFirstSurfacePermissions(
|
|
184
|
+
ctx.principal.id,
|
|
185
|
+
ctx.accountId!,
|
|
186
|
+
'app',
|
|
187
|
+
'create'
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if (!perms.canCreate) {
|
|
191
|
+
throw new Error('Insufficient permissions to create apps')
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check if slug is unique
|
|
196
|
+
const { data: existing } = await ctx.db
|
|
197
|
+
.from('apps')
|
|
198
|
+
.select('id')
|
|
199
|
+
.eq('slug', slug)
|
|
200
|
+
.single()
|
|
201
|
+
|
|
202
|
+
if (existing) {
|
|
203
|
+
throw new Error('App slug already exists')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const { data, error: err } = await ctx.db
|
|
207
|
+
.from('apps')
|
|
208
|
+
.insert({
|
|
209
|
+
slug,
|
|
210
|
+
name,
|
|
211
|
+
description,
|
|
212
|
+
icon,
|
|
213
|
+
color,
|
|
214
|
+
version: version || '1.0.0',
|
|
215
|
+
app_type: app_type || 'custom',
|
|
216
|
+
source: source || 'custom',
|
|
217
|
+
owner_account_id: owner_account_id || ctx.accountId,
|
|
218
|
+
config: config || {},
|
|
219
|
+
nav_items: nav_items || [],
|
|
220
|
+
min_role: min_role || 'member',
|
|
221
|
+
integration_deps: integration_deps || [],
|
|
222
|
+
metadata: metadata || {},
|
|
223
|
+
route_prefix: route_prefix !== undefined ? route_prefix : ('/' + slug),
|
|
224
|
+
renderer: renderer || 'generic',
|
|
225
|
+
is_active: true,
|
|
226
|
+
is_system: false
|
|
227
|
+
})
|
|
228
|
+
.select()
|
|
229
|
+
.single()
|
|
230
|
+
|
|
231
|
+
if (err) throw err
|
|
232
|
+
|
|
233
|
+
await emitLog(ctx, 'app.created', { type: 'app', id: data.id }, { after: data })
|
|
234
|
+
|
|
235
|
+
return data
|
|
236
|
+
})
|
|
237
|
+
// ─── CHUNK_END: APPS_CREATE ────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
// ─── CHUNK_START: APPS_UPDATE ──────────────────────────────────────────────
|
|
240
|
+
/**
|
|
241
|
+
* @chunk-id APPS_UPDATE_1_0_0
|
|
242
|
+
* @version 1.0.0
|
|
243
|
+
* @hash 77029f18737fee801ff5ebdde199d6b52454fd9fe3842694b072181fc713b937
|
|
244
|
+
* @macro App Update Handler
|
|
245
|
+
* @micro Updates app with field-level permission validation and audit logging
|
|
246
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
247
|
+
* @inputs body: object — App updates including id
|
|
248
|
+
* @outputs Updated app record
|
|
249
|
+
* @depends-on [createHandler, permissions, emitLog]
|
|
250
|
+
* @depended-by [Netlify function routing]
|
|
251
|
+
* @side-effects [DB update, permission validation, audit logging]
|
|
252
|
+
* @tags apps, update, crud, permissions, audit
|
|
253
|
+
*/
|
|
254
|
+
export const update = createHandler(async (ctx, body) => {
|
|
255
|
+
const id = body?.id || ctx.query?.id
|
|
256
|
+
const { id: _bodyId, ...updates } = body || {}
|
|
257
|
+
|
|
258
|
+
if (!id) {
|
|
259
|
+
throw new Error('App ID is required')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!ctx.principal || ctx.principal.id === 'anonymous' || !ctx.accountId) {
|
|
263
|
+
throw new Error('User context (person and account) required')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Get current state for audit - RLS will filter to accessible apps
|
|
267
|
+
const { data: current } = await ctx.db
|
|
268
|
+
.from('apps')
|
|
269
|
+
.select('*')
|
|
270
|
+
.eq('id', id)
|
|
271
|
+
.single()
|
|
272
|
+
|
|
273
|
+
if (!current) {
|
|
274
|
+
throw new Error('App not found')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Validate field-level permissions
|
|
278
|
+
const fieldValidation = await permissions.validateUpdatePermissions(
|
|
279
|
+
ctx,
|
|
280
|
+
updates,
|
|
281
|
+
current,
|
|
282
|
+
'apps'
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if (!fieldValidation.valid) {
|
|
286
|
+
throw new Error(fieldValidation.error)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const { data, error: err } = await ctx.db
|
|
290
|
+
.from('apps')
|
|
291
|
+
.update({
|
|
292
|
+
...updates,
|
|
293
|
+
updated_at: new Date().toISOString()
|
|
294
|
+
})
|
|
295
|
+
.eq('id', id)
|
|
296
|
+
.select()
|
|
297
|
+
.single()
|
|
298
|
+
|
|
299
|
+
if (err) throw err
|
|
300
|
+
|
|
301
|
+
await emitLog(ctx, 'app.updated', { type: 'app', id }, { before: current, after: data })
|
|
302
|
+
|
|
303
|
+
return data
|
|
304
|
+
})
|
|
305
|
+
// ─── CHUNK_END: APPS_UPDATE ────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
// ─── CHUNK_START: APPS_REMOVE ──────────────────────────────────────────────
|
|
308
|
+
/**
|
|
309
|
+
* @chunk-id APPS_REMOVE_1_0_0
|
|
310
|
+
* @version 1.0.0
|
|
311
|
+
* @hash b2c79779820861406f1adabc2279e332727b936abab4c6f5e5762a62d1373169
|
|
312
|
+
* @macro App Remove Handler
|
|
313
|
+
* @micro Soft-deletes app with validation and audit logging
|
|
314
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
315
|
+
* @inputs body: any — Request body with app ID
|
|
316
|
+
* @outputs Updated app record with is_active: false
|
|
317
|
+
* @depends-on [createHandler, emitLog]
|
|
318
|
+
* @depended-by [Netlify function routing]
|
|
319
|
+
* @side-effects [DB soft delete, audit logging]
|
|
320
|
+
* @tags apps, remove, crud, audit
|
|
321
|
+
*/
|
|
322
|
+
export const remove = createHandler(async (ctx, body) => {
|
|
323
|
+
const id = body?.id || ctx.query?.id
|
|
324
|
+
|
|
325
|
+
if (!id) {
|
|
326
|
+
throw new Error('App ID is required')
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!ctx.principal || ctx.principal.id === 'anonymous' || !ctx.accountId) {
|
|
330
|
+
throw new Error('User context (person and account) required')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Get current state for audit - RLS will filter to accessible apps
|
|
334
|
+
const { data: current } = await ctx.db
|
|
335
|
+
.from('apps')
|
|
336
|
+
.select('*')
|
|
337
|
+
.eq('id', id)
|
|
338
|
+
.single()
|
|
339
|
+
|
|
340
|
+
if (!current) {
|
|
341
|
+
throw new Error('App not found')
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const { data, error: err } = await ctx.db
|
|
345
|
+
.from('apps')
|
|
346
|
+
.update({
|
|
347
|
+
is_active: false,
|
|
348
|
+
updated_at: new Date().toISOString()
|
|
349
|
+
})
|
|
350
|
+
.eq('id', id)
|
|
351
|
+
.select()
|
|
352
|
+
.single()
|
|
353
|
+
|
|
354
|
+
if (err) throw err
|
|
355
|
+
|
|
356
|
+
await emitLog(ctx, 'app.deleted', { type: 'app', id }, { before: current })
|
|
357
|
+
|
|
358
|
+
return data
|
|
359
|
+
})
|
|
360
|
+
// ─── CHUNK_END: APPS_REMOVE ────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
// ─── CHUNK_START: APPS_CHECK_AVAILABILITY ──────────────────────────────────────────────
|
|
363
|
+
/**
|
|
364
|
+
* @chunk-id APPS_CHECK_AVAILABILITY_1_0_0
|
|
365
|
+
* @version 1.0.0
|
|
366
|
+
* @hash db7d52bcb1002d923dac039a99e6f6fb0eb9889e9b4a44994bf7bb75477ac9a4
|
|
367
|
+
* @macro App Availability Checker
|
|
368
|
+
* @micro Checks if app is available for account via RPC
|
|
369
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
370
|
+
* @inputs body: any — Request body (unused for GET)
|
|
371
|
+
* @outputs {available: boolean} — Availability status
|
|
372
|
+
* @depends-on [createHandler]
|
|
373
|
+
* @depended-by [Netlify function routing]
|
|
374
|
+
* @side-effects [RPC call for availability check]
|
|
375
|
+
* @tags apps, availability, rpc, check
|
|
376
|
+
*/
|
|
377
|
+
export const checkAvailability = createHandler(async (ctx, body) => {
|
|
378
|
+
const { slug } = ctx.query || {}
|
|
379
|
+
|
|
380
|
+
if (!slug) {
|
|
381
|
+
throw new Error('App slug is required')
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!ctx.accountId) {
|
|
385
|
+
throw new Error('Account context required')
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const { data, error: err } = await ctx.db
|
|
389
|
+
.rpc('is_app_available', {
|
|
390
|
+
app_slug: slug,
|
|
391
|
+
account_id: ctx.accountId
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
if (err) throw err
|
|
395
|
+
|
|
396
|
+
return { available: data }
|
|
397
|
+
})
|
|
398
|
+
// ─── CHUNK_END: APPS_CHECK_AVAILABILITY ────────────────────────────────────────────────
|
|
399
|
+
|
|
400
|
+
// ─── CHUNK_START: APPS_UPDATE_VERSION ──────────────────────────────────────────────
|
|
401
|
+
/**
|
|
402
|
+
* @chunk-id APPS_UPDATE_VERSION_1_0_0
|
|
403
|
+
* @version 1.0.0
|
|
404
|
+
* @hash 04a1a3367e4b8ca0f58e0719e73b0ccda378e84b4b874f3f7e07ee0faa423bfd
|
|
405
|
+
* @macro App Version Update Handler
|
|
406
|
+
* @micro Updates app version string via RPC with audit logging
|
|
407
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
408
|
+
* @inputs body: object — App ID and new version string
|
|
409
|
+
* @outputs {success: true} — Success confirmation
|
|
410
|
+
* @depends-on [createHandler, emitLog]
|
|
411
|
+
* @depended-by [Netlify function routing]
|
|
412
|
+
* @side-effects [RPC call, audit logging]
|
|
413
|
+
* @tags apps, version, update, rpc, audit
|
|
414
|
+
*/
|
|
415
|
+
export const updateVersion = createHandler(async (ctx, body) => {
|
|
416
|
+
const { id, version } = body
|
|
417
|
+
|
|
418
|
+
if (!id || !version) {
|
|
419
|
+
throw new Error('App ID and version are required')
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const { data, error: err } = await ctx.db
|
|
423
|
+
.rpc('update_app_version', {
|
|
424
|
+
app_id: id,
|
|
425
|
+
new_version: version
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
if (err) throw err
|
|
429
|
+
|
|
430
|
+
await emitLog(ctx, 'app.version_updated', { type: 'app', id }, { after: { version } })
|
|
431
|
+
|
|
432
|
+
return { success: true }
|
|
433
|
+
})
|
|
434
|
+
// ─── CHUNK_END: APPS_UPDATE_VERSION ────────────────────────────────────────────────
|
|
435
|
+
|
|
436
|
+
// ─── MAIN HANDLER ────────────────────────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
// ─── CHUNK_START: APPS_HANDLER ──────────────────────────────────────────────
|
|
439
|
+
/**
|
|
440
|
+
* @chunk-id APPS_HANDLER_1_0_0
|
|
441
|
+
* @version 1.0.0
|
|
442
|
+
* @hash 361e1ba86b38a4611f3170526e4ff7f456d58b929c1d39bf847135afdebf3867
|
|
443
|
+
* @macro Apps Router
|
|
444
|
+
* @micro Routes HTTP methods and actions to appropriate handlers
|
|
445
|
+
* @inputs ctx: CoreContext — Request context with principal and database
|
|
446
|
+
* @inputs body: any — Request body for POST/PATCH operations
|
|
447
|
+
* @outputs Varies — Depends on routed handler (list/get/create/update/remove/schema/available/version)
|
|
448
|
+
* @depends-on [createHandler, list, get, getSchema, checkAvailability, create, update, remove, updateVersion]
|
|
449
|
+
* @depended-by [Netlify function routing]
|
|
450
|
+
* @side-effects [Delegates to appropriate handler]
|
|
451
|
+
* @tags apps, router, crud, netlify-function
|
|
452
|
+
*/
|
|
453
|
+
export const handler = createHandler(async (ctx, body) => {
|
|
454
|
+
const method = ctx.query?.method || 'GET'
|
|
455
|
+
|
|
456
|
+
switch (method) {
|
|
457
|
+
case 'GET':
|
|
458
|
+
if (ctx.query?.action === 'get' || ctx.query?.id) {
|
|
459
|
+
return await get(ctx, body)
|
|
460
|
+
} else if (ctx.query?.slug) {
|
|
461
|
+
return await get(ctx, body)
|
|
462
|
+
} else if (ctx.query?.action === 'schema') {
|
|
463
|
+
return await getSchema(ctx, body)
|
|
464
|
+
} else if (ctx.query?.action === 'available') {
|
|
465
|
+
return await checkAvailability(ctx, body)
|
|
466
|
+
} else {
|
|
467
|
+
return await list(ctx, body)
|
|
468
|
+
}
|
|
469
|
+
case 'POST':
|
|
470
|
+
if (ctx.query?.action === 'version') {
|
|
471
|
+
return await updateVersion(ctx, body)
|
|
472
|
+
} else {
|
|
473
|
+
return await create(ctx, body)
|
|
474
|
+
}
|
|
475
|
+
case 'PATCH':
|
|
476
|
+
return await update(ctx, body)
|
|
477
|
+
case 'DELETE':
|
|
478
|
+
return await remove(ctx, body)
|
|
479
|
+
default:
|
|
480
|
+
throw new Error(`Unsupported method: ${method}`)
|
|
481
|
+
}
|
|
482
|
+
})
|
|
483
|
+
// ─── CHUNK_END: APPS_HANDLER ────────────────────────────────────────────────
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module auth
|
|
3
|
+
* @audience core-contributor
|
|
4
|
+
* @layer api-handler
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Authentication context endpoint. Returns the full user session context for
|
|
8
|
+
* the authenticated principal: person record, account, role, permissions, and
|
|
9
|
+
* accessible child accounts (via the `get_account_hierarchy` RPC).
|
|
10
|
+
*
|
|
11
|
+
* **Routed by:** `GET /.netlify/functions/auth`
|
|
12
|
+
*
|
|
13
|
+
* **Actions:**
|
|
14
|
+
* | method | handler |
|
|
15
|
+
* |--------|----------|
|
|
16
|
+
* | GET | context |
|
|
17
|
+
* | HEALTH | health |
|
|
18
|
+
*
|
|
19
|
+
* **Authorization:** `context` requires a non-anonymous authenticated
|
|
20
|
+
* principal. `health` is unauthenticated.
|
|
21
|
+
*
|
|
22
|
+
* **Returned session shape:**
|
|
23
|
+
* ```ts
|
|
24
|
+
* {
|
|
25
|
+
* id: string // person UUID
|
|
26
|
+
* email: string
|
|
27
|
+
* full_name: string
|
|
28
|
+
* account_id: string
|
|
29
|
+
* account: { id, slug, display_name, parent_id }
|
|
30
|
+
* roles: string[] // role slugs (e.g. ['system_admin'])
|
|
31
|
+
* permissions: string[] // derived from role.slug or role.permissions
|
|
32
|
+
* accessible_accounts: AccountHierarchyRow[]
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* INVARIANT: `system_admin` role always receives the full permission set
|
|
37
|
+
* ['read', 'write', 'admin', 'system'] regardless of `role.permissions`.
|
|
38
|
+
*
|
|
39
|
+
* @seeAlso middleware.ts (createHandler builds ctx.principal from JWT)
|
|
40
|
+
* @seeAlso _shared/db.ts (get_account_hierarchy RPC)
|
|
41
|
+
* @seeAlso roles.ts (role records referenced by FK)
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { createHandler } from './_shared/middleware'
|
|
45
|
+
|
|
46
|
+
// ─── HANDLERS ─────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns the full authenticated user context. Fetches person, account,
|
|
50
|
+
* and role in a single query, then calls `get_account_hierarchy` to
|
|
51
|
+
* resolve accessible child accounts.
|
|
52
|
+
*
|
|
53
|
+
* @returns Session object (see module-level shape doc)
|
|
54
|
+
* @throws Error('Authentication required') if principal is anonymous
|
|
55
|
+
* @throws Error('User not found: <id>') if person row is inactive or missing
|
|
56
|
+
* @sideEffects DB read: people table (with account + role joins)
|
|
57
|
+
* @sideEffects DB read: get_account_hierarchy RPC
|
|
58
|
+
* @calledBy handler (GET)
|
|
59
|
+
* @testUnit tests/unit/auth.test.ts — 'context'
|
|
60
|
+
* @testIntegration tests/integration/auth.test.ts — 'returns valid context'
|
|
61
|
+
*/
|
|
62
|
+
export const context = createHandler(async (ctx, _body) => {
|
|
63
|
+
// Authentication is required
|
|
64
|
+
if (!ctx.principal || ctx.principal.id === 'anonymous') {
|
|
65
|
+
throw new Error('Authentication required')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log('Backend: Fetching user context for person:', ctx.principal.id)
|
|
69
|
+
|
|
70
|
+
// Single query gets everything - person, account, role using simplified model
|
|
71
|
+
const { data: personData, error: personError } = await ctx.db
|
|
72
|
+
.from('people')
|
|
73
|
+
.select(`
|
|
74
|
+
id,
|
|
75
|
+
email,
|
|
76
|
+
full_name,
|
|
77
|
+
avatar_url,
|
|
78
|
+
account_id,
|
|
79
|
+
role_id,
|
|
80
|
+
account:accounts!people_account_id_fkey(
|
|
81
|
+
id,
|
|
82
|
+
slug,
|
|
83
|
+
display_name,
|
|
84
|
+
parent_id
|
|
85
|
+
),
|
|
86
|
+
role:roles(
|
|
87
|
+
id,
|
|
88
|
+
slug,
|
|
89
|
+
name,
|
|
90
|
+
is_system
|
|
91
|
+
)
|
|
92
|
+
`)
|
|
93
|
+
.eq('id', ctx.principal.id)
|
|
94
|
+
.eq('is_active', true)
|
|
95
|
+
.single()
|
|
96
|
+
|
|
97
|
+
if (personError) {
|
|
98
|
+
console.error('Backend: Error fetching person:', personError)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!personData) {
|
|
102
|
+
throw new Error('User not found: ' + ctx.principal.id)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('Backend: Person data found:', {
|
|
106
|
+
id: personData.id,
|
|
107
|
+
email: personData.email,
|
|
108
|
+
account_id: personData.account_id,
|
|
109
|
+
role_id: personData.role_id
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Extract account and role (handle array responses)
|
|
113
|
+
const account = Array.isArray(personData.account) ? personData.account[0] : personData.account
|
|
114
|
+
const role = Array.isArray(personData.role) ? personData.role[0] : personData.role
|
|
115
|
+
|
|
116
|
+
// Get child accounts recursively
|
|
117
|
+
let accessibleAccounts = []
|
|
118
|
+
if (personData.account_id) {
|
|
119
|
+
const { data: childAccounts } = await ctx.db
|
|
120
|
+
.rpc('get_account_hierarchy', {
|
|
121
|
+
parent_account_id: personData.account_id
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
accessibleAccounts = childAccounts || []
|
|
125
|
+
console.log('Backend: Found', accessibleAccounts.length, 'accessible accounts')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Determine permissions from role - system_admin role has full permissions
|
|
129
|
+
let effectivePermissions = []
|
|
130
|
+
const roleSlug = role?.slug
|
|
131
|
+
|
|
132
|
+
if (roleSlug === 'system_admin') {
|
|
133
|
+
effectivePermissions = ['read', 'write', 'admin', 'system']
|
|
134
|
+
} else if (role?.permissions && Array.isArray(role.permissions)) {
|
|
135
|
+
effectivePermissions = role.permissions
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log('Backend: User context complete:', {
|
|
139
|
+
id: personData.id,
|
|
140
|
+
email: personData.email,
|
|
141
|
+
account: account?.slug,
|
|
142
|
+
role: roleSlug,
|
|
143
|
+
permissionsCount: effectivePermissions.length
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Return complete user context
|
|
147
|
+
// Note: system_admin status is determined by 'system_admin' in roles array
|
|
148
|
+
return {
|
|
149
|
+
id: personData.id,
|
|
150
|
+
email: personData.email,
|
|
151
|
+
full_name: personData.full_name,
|
|
152
|
+
account_id: personData.account_id,
|
|
153
|
+
account: account,
|
|
154
|
+
roles: [roleSlug].filter(Boolean),
|
|
155
|
+
permissions: effectivePermissions,
|
|
156
|
+
is_system_admin: roleSlug === 'system_admin',
|
|
157
|
+
accessible_accounts: accessibleAccounts
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Lightweight health check for the auth function. Returns service name
|
|
163
|
+
* and current timestamp. No authentication required.
|
|
164
|
+
*
|
|
165
|
+
* @returns `{ status: 'healthy', timestamp: string, service: 'auth' }`
|
|
166
|
+
* @calledBy handler (HEALTH method)
|
|
167
|
+
* @calledBy load balancer health probes
|
|
168
|
+
*/
|
|
169
|
+
export const health = createHandler(async (ctx, _body) => {
|
|
170
|
+
return {
|
|
171
|
+
status: 'healthy',
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
service: 'auth'
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// ─── MAIN HANDLER ────────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Netlify function entry point. Routes by HTTP method:
|
|
181
|
+
* GET → context | HEALTH → health
|
|
182
|
+
* @throws Error('Unsupported method: <method>') on unmatched method
|
|
183
|
+
* @calledBy Netlify function routing
|
|
184
|
+
*/
|
|
185
|
+
export const handler = createHandler(async (ctx, _body) => {
|
|
186
|
+
const method = ctx.query?.method || 'GET'
|
|
187
|
+
|
|
188
|
+
switch (method) {
|
|
189
|
+
case 'GET':
|
|
190
|
+
return await context(ctx, _body)
|
|
191
|
+
case 'HEALTH':
|
|
192
|
+
return await health(ctx, _body)
|
|
193
|
+
default:
|
|
194
|
+
throw new Error(`Unsupported method: ${method}`)
|
|
195
|
+
}
|
|
196
|
+
})
|