@kelpie/server 0.11.0 → 0.13.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/dist/app.d.ts +13 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +8 -1
- package/dist/app.js.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/boot.js +1 -0
- package/dist/boot.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/apiKeyScopes.d.ts.map +1 -1
- package/dist/lib/apiKeyScopes.js +6 -0
- package/dist/lib/apiKeyScopes.js.map +1 -1
- package/dist/lib/columns.d.ts +6 -0
- package/dist/lib/columns.d.ts.map +1 -1
- package/dist/lib/columns.js +5 -3
- package/dist/lib/columns.js.map +1 -1
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/egress.d.ts.map +1 -1
- package/dist/lib/egress.js +8 -4
- package/dist/lib/egress.js.map +1 -1
- package/dist/lib/errors.d.ts +18 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +25 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/ids.d.ts +11 -0
- package/dist/lib/ids.d.ts.map +1 -1
- package/dist/lib/ids.js +11 -0
- package/dist/lib/ids.js.map +1 -1
- package/dist/lib/kelpieConfigFile.d.ts +15 -1
- package/dist/lib/kelpieConfigFile.d.ts.map +1 -1
- package/dist/lib/kelpieConfigFile.js +33 -1
- package/dist/lib/kelpieConfigFile.js.map +1 -1
- package/dist/modules/agent-tasks/catalog.d.ts.map +1 -1
- package/dist/modules/agent-tasks/catalog.js +32 -1
- package/dist/modules/agent-tasks/catalog.js.map +1 -1
- package/dist/modules/agent-tasks/index.d.ts.map +1 -1
- package/dist/modules/agent-tasks/index.js +1 -0
- package/dist/modules/agent-tasks/index.js.map +1 -1
- package/dist/modules/agent-tasks/resolve.d.ts.map +1 -1
- package/dist/modules/agent-tasks/resolve.js +5 -0
- package/dist/modules/agent-tasks/resolve.js.map +1 -1
- package/dist/modules/agent-tasks/routes.d.ts +2 -0
- package/dist/modules/agent-tasks/routes.d.ts.map +1 -1
- package/dist/modules/attachedRecords.d.ts +22 -8
- package/dist/modules/attachedRecords.d.ts.map +1 -1
- package/dist/modules/attachedRecords.js +42 -5
- package/dist/modules/attachedRecords.js.map +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +22 -4
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/repository.d.ts +16 -1
- package/dist/modules/auth/repository.d.ts.map +1 -1
- package/dist/modules/auth/repository.js +21 -2
- package/dist/modules/auth/repository.js.map +1 -1
- package/dist/modules/auth/routes.d.ts.map +1 -1
- package/dist/modules/auth/routes.js +32 -6
- package/dist/modules/auth/routes.js.map +1 -1
- package/dist/modules/auth/schema.d.ts +18 -1
- package/dist/modules/auth/schema.d.ts.map +1 -1
- package/dist/modules/auth/schema.js +12 -1
- package/dist/modules/auth/schema.js.map +1 -1
- package/dist/modules/auth/service.d.ts +46 -0
- package/dist/modules/auth/service.d.ts.map +1 -1
- package/dist/modules/auth/service.js +167 -10
- package/dist/modules/auth/service.js.map +1 -1
- package/dist/modules/auth/session.d.ts +12 -0
- package/dist/modules/auth/session.d.ts.map +1 -1
- package/dist/modules/auth/session.js +12 -0
- package/dist/modules/auth/session.js.map +1 -1
- package/dist/modules/companies/repository.d.ts.map +1 -1
- package/dist/modules/companies/repository.js +1 -1
- package/dist/modules/companies/repository.js.map +1 -1
- package/dist/modules/companies/routes.d.ts +2 -4
- package/dist/modules/companies/routes.d.ts.map +1 -1
- package/dist/modules/companies/routes.js +6 -11
- package/dist/modules/companies/routes.js.map +1 -1
- package/dist/modules/companies/schema.d.ts +13 -28
- package/dist/modules/companies/schema.d.ts.map +1 -1
- package/dist/modules/companies/schema.js +2 -2
- package/dist/modules/companies/schema.js.map +1 -1
- package/dist/modules/companies/service.d.ts +2 -3
- package/dist/modules/companies/service.d.ts.map +1 -1
- package/dist/modules/companies/service.js +4 -7
- package/dist/modules/companies/service.js.map +1 -1
- package/dist/modules/core.d.ts.map +1 -1
- package/dist/modules/core.js +2 -0
- package/dist/modules/core.js.map +1 -1
- package/dist/modules/custom-fields/repository.d.ts.map +1 -1
- package/dist/modules/custom-fields/repository.js +2 -0
- package/dist/modules/custom-fields/repository.js.map +1 -1
- package/dist/modules/custom-fields/routes.d.ts +1 -0
- package/dist/modules/custom-fields/routes.d.ts.map +1 -1
- package/dist/modules/dashboard/index.d.ts.map +1 -1
- package/dist/modules/dashboard/index.js +1 -0
- package/dist/modules/dashboard/index.js.map +1 -1
- package/dist/modules/dashboard/repository.d.ts +13 -0
- package/dist/modules/dashboard/repository.d.ts.map +1 -1
- package/dist/modules/dashboard/repository.js +36 -0
- package/dist/modules/dashboard/repository.js.map +1 -1
- package/dist/modules/dashboard/routes.d.ts.map +1 -1
- package/dist/modules/dashboard/routes.js +13 -0
- package/dist/modules/dashboard/routes.js.map +1 -1
- package/dist/modules/dashboard/service.d.ts +11 -0
- package/dist/modules/dashboard/service.d.ts.map +1 -1
- package/dist/modules/dashboard/service.js +24 -1
- package/dist/modules/dashboard/service.js.map +1 -1
- package/dist/modules/decisions/repository.d.ts.map +1 -1
- package/dist/modules/decisions/repository.js +2 -0
- package/dist/modules/decisions/repository.js.map +1 -1
- package/dist/modules/decisions/routes.d.ts +2 -0
- package/dist/modules/decisions/routes.d.ts.map +1 -1
- package/dist/modules/events/attendances.d.ts +35 -0
- package/dist/modules/events/attendances.d.ts.map +1 -0
- package/dist/modules/events/attendances.js +157 -0
- package/dist/modules/events/attendances.js.map +1 -0
- package/dist/modules/events/catalog.d.ts +35 -0
- package/dist/modules/events/catalog.d.ts.map +1 -0
- package/dist/modules/events/catalog.js +11 -0
- package/dist/modules/events/catalog.js.map +1 -0
- package/dist/modules/events/index.d.ts +10 -0
- package/dist/modules/events/index.d.ts.map +1 -0
- package/dist/modules/events/index.js +47 -0
- package/dist/modules/events/index.js.map +1 -0
- package/dist/modules/events/repository.d.ts +67 -0
- package/dist/modules/events/repository.d.ts.map +1 -0
- package/dist/modules/events/repository.js +212 -0
- package/dist/modules/events/repository.js.map +1 -0
- package/dist/modules/events/routes.d.ts +136 -0
- package/dist/modules/events/routes.d.ts.map +1 -0
- package/dist/modules/events/routes.js +280 -0
- package/dist/modules/events/routes.js.map +1 -0
- package/dist/modules/events/schema.d.ts +626 -0
- package/dist/modules/events/schema.d.ts.map +1 -0
- package/dist/modules/events/schema.js +93 -0
- package/dist/modules/events/schema.js.map +1 -0
- package/dist/modules/events/service.d.ts +68 -0
- package/dist/modules/events/service.d.ts.map +1 -0
- package/dist/modules/events/service.js +262 -0
- package/dist/modules/events/service.js.map +1 -0
- package/dist/modules/events/tools.d.ts +8 -0
- package/dist/modules/events/tools.d.ts.map +1 -0
- package/dist/modules/events/tools.js +80 -0
- package/dist/modules/events/tools.js.map +1 -0
- package/dist/modules/forms/applySubmissionFields.d.ts.map +1 -1
- package/dist/modules/forms/applySubmissionFields.js +7 -5
- package/dist/modules/forms/applySubmissionFields.js.map +1 -1
- package/dist/modules/forms/fillAddresses.d.ts +4 -0
- package/dist/modules/forms/fillAddresses.d.ts.map +1 -0
- package/dist/modules/forms/fillAddresses.js +93 -0
- package/dist/modules/forms/fillAddresses.js.map +1 -0
- package/dist/modules/forms/repository.d.ts +2 -2
- package/dist/modules/forms/repository.d.ts.map +1 -1
- package/dist/modules/forms/repository.js.map +1 -1
- package/dist/modules/forms/routes.d.ts +2 -0
- package/dist/modules/forms/routes.d.ts.map +1 -1
- package/dist/modules/forms/routes.js +2 -2
- package/dist/modules/forms/routes.js.map +1 -1
- package/dist/modules/forms/schema.js +2 -2
- package/dist/modules/forms/schema.js.map +1 -1
- package/dist/modules/forms/service.js +2 -2
- package/dist/modules/forms/service.js.map +1 -1
- package/dist/modules/forms/submission.d.ts +1 -0
- package/dist/modules/forms/submission.d.ts.map +1 -1
- package/dist/modules/forms/submission.js +27 -4
- package/dist/modules/forms/submission.js.map +1 -1
- package/dist/modules/hiring/roles.d.ts.map +1 -1
- package/dist/modules/hiring/roles.js +2 -1
- package/dist/modules/hiring/roles.js.map +1 -1
- package/dist/modules/import-export/addresses.d.ts +22 -0
- package/dist/modules/import-export/addresses.d.ts.map +1 -0
- package/dist/modules/import-export/addresses.js +83 -0
- package/dist/modules/import-export/addresses.js.map +1 -0
- package/dist/modules/import-export/drafts.d.ts +3 -4
- package/dist/modules/import-export/drafts.d.ts.map +1 -1
- package/dist/modules/import-export/drafts.js +4 -5
- package/dist/modules/import-export/drafts.js.map +1 -1
- package/dist/modules/import-export/exportRows.d.ts.map +1 -1
- package/dist/modules/import-export/exportRows.js +3 -3
- package/dist/modules/import-export/exportRows.js.map +1 -1
- package/dist/modules/import-export/plan.d.ts.map +1 -1
- package/dist/modules/import-export/plan.js +17 -2
- package/dist/modules/import-export/plan.js.map +1 -1
- package/dist/modules/import-export/presets.d.ts.map +1 -1
- package/dist/modules/import-export/presets.js +52 -7
- package/dist/modules/import-export/presets.js.map +1 -1
- package/dist/modules/import-export/repository.d.ts +3 -4
- package/dist/modules/import-export/repository.d.ts.map +1 -1
- package/dist/modules/import-export/repository.js +2 -3
- package/dist/modules/import-export/repository.js.map +1 -1
- package/dist/modules/import-export/writes.d.ts.map +1 -1
- package/dist/modules/import-export/writes.js +8 -4
- package/dist/modules/import-export/writes.js.map +1 -1
- package/dist/modules/lists/routes.d.ts +4 -0
- package/dist/modules/lists/routes.d.ts.map +1 -1
- package/dist/modules/notes/routes.d.ts +2 -0
- package/dist/modules/notes/routes.d.ts.map +1 -1
- package/dist/modules/people/repository.d.ts.map +1 -1
- package/dist/modules/people/repository.js +1 -1
- package/dist/modules/people/repository.js.map +1 -1
- package/dist/modules/people/routes.d.ts +2 -2
- package/dist/modules/people/routes.js +6 -6
- package/dist/modules/people/routes.js.map +1 -1
- package/dist/modules/people/schema.d.ts +17 -14
- package/dist/modules/people/schema.d.ts.map +1 -1
- package/dist/modules/people/schema.js +6 -4
- package/dist/modules/people/schema.js.map +1 -1
- package/dist/modules/people/service.d.ts +2 -2
- package/dist/modules/people/service.d.ts.map +1 -1
- package/dist/modules/people/service.js +5 -4
- package/dist/modules/people/service.js.map +1 -1
- package/dist/modules/plans/repository.d.ts +2 -2
- package/dist/modules/plans/repository.d.ts.map +1 -1
- package/dist/modules/plans/routes.d.ts +1 -0
- package/dist/modules/plans/routes.d.ts.map +1 -1
- package/dist/modules/plans/routes.js +4 -4
- package/dist/modules/plans/routes.js.map +1 -1
- package/dist/modules/plans/schema.d.ts +2 -2
- package/dist/modules/plans/schema.d.ts.map +1 -1
- package/dist/modules/plans/schema.js +3 -3
- package/dist/modules/plans/schema.js.map +1 -1
- package/dist/modules/plans/service.d.ts +2 -2
- package/dist/modules/plans/service.d.ts.map +1 -1
- package/dist/modules/plans/service.js.map +1 -1
- package/dist/modules/plans/tools.js +2 -2
- package/dist/modules/plans/tools.js.map +1 -1
- package/dist/modules/recordTargets.d.ts.map +1 -1
- package/dist/modules/recordTargets.js +11 -0
- package/dist/modules/recordTargets.js.map +1 -1
- package/dist/modules/references.d.ts.map +1 -1
- package/dist/modules/references.js +2 -0
- package/dist/modules/references.js.map +1 -1
- package/dist/modules/sample-data/fixture.d.ts +26 -4
- package/dist/modules/sample-data/fixture.d.ts.map +1 -1
- package/dist/modules/sample-data/fixture.js +157 -23
- package/dist/modules/sample-data/fixture.js.map +1 -1
- package/dist/modules/sample-data/index.d.ts +5 -2
- package/dist/modules/sample-data/index.d.ts.map +1 -1
- package/dist/modules/sample-data/index.js +7 -2
- package/dist/modules/sample-data/index.js.map +1 -1
- package/dist/modules/sample-data/routes.d.ts.map +1 -1
- package/dist/modules/sample-data/routes.js +2 -0
- package/dist/modules/sample-data/routes.js.map +1 -1
- package/dist/modules/sample-data/service.d.ts +11 -3
- package/dist/modules/sample-data/service.d.ts.map +1 -1
- package/dist/modules/sample-data/service.js +452 -363
- package/dist/modules/sample-data/service.js.map +1 -1
- package/dist/modules/sample-data/tools.d.ts.map +1 -1
- package/dist/modules/sample-data/tools.js +4 -3
- package/dist/modules/sample-data/tools.js.map +1 -1
- package/dist/modules/search/index.d.ts +1 -1
- package/dist/modules/search/index.d.ts.map +1 -1
- package/dist/modules/search/index.js +2 -1
- package/dist/modules/search/index.js.map +1 -1
- package/dist/modules/search/repository.d.ts +1 -0
- package/dist/modules/search/repository.d.ts.map +1 -1
- package/dist/modules/search/repository.js +22 -0
- package/dist/modules/search/repository.js.map +1 -1
- package/dist/modules/search/service.d.ts +2 -2
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +4 -3
- package/dist/modules/search/service.js.map +1 -1
- package/dist/modules/smtp-email/index.d.ts +12 -4
- package/dist/modules/smtp-email/index.d.ts.map +1 -1
- package/dist/modules/smtp-email/index.js +25 -5
- package/dist/modules/smtp-email/index.js.map +1 -1
- package/dist/modules/workspace/repository.d.ts +2 -0
- package/dist/modules/workspace/repository.d.ts.map +1 -1
- package/dist/modules/workspace/repository.js +12 -0
- package/dist/modules/workspace/repository.js.map +1 -1
- package/dist/modules/workspace/routes.d.ts.map +1 -1
- package/dist/modules/workspace/routes.js +13 -0
- package/dist/modules/workspace/routes.js.map +1 -1
- package/dist/modules/workspace/service.d.ts +10 -0
- package/dist/modules/workspace/service.d.ts.map +1 -1
- package/dist/modules/workspace/service.js +34 -10
- package/dist/modules/workspace/service.js.map +1 -1
- package/dist/modules/workspace/starters.d.ts +17 -2
- package/dist/modules/workspace/starters.d.ts.map +1 -1
- package/dist/modules/workspace/starters.js +141 -14
- package/dist/modules/workspace/starters.js.map +1 -1
- package/dist/modules/workspace/workspaceAccessMiddleware.d.ts.map +1 -1
- package/dist/modules/workspace/workspaceAccessMiddleware.js +3 -4
- package/dist/modules/workspace/workspaceAccessMiddleware.js.map +1 -1
- package/dist/runtime/events.d.ts +1 -1
- package/dist/runtime/events.d.ts.map +1 -1
- package/dist/runtime/events.js +2 -0
- package/dist/runtime/events.js.map +1 -1
- package/dist/runtime/module.d.ts +71 -1
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +142 -9
- package/dist/runtime/registry.js.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/app.d.ts +8 -0
- package/dist/testing/app.d.ts.map +1 -1
- package/dist/testing/app.js +6 -1
- package/dist/testing/app.js.map +1 -1
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +9 -1
- package/dist/testing/client.js.map +1 -1
- package/migrations/0043_external_sign_in.sql +2 -0
- package/migrations/0044_events.sql +79 -0
- package/migrations/0045_addresses.sql +66 -0
- package/migrations/0046_drop_company_website.sql +66 -0
- package/migrations/meta/_journal.json +28 -0
- package/package.json +3 -3
- package/src/app.ts +25 -1
- package/src/boot.ts +1 -0
- package/src/index.ts +13 -1
- package/src/lib/apiKeyScopes.ts +6 -0
- package/src/lib/columns.ts +12 -3
- package/src/lib/config.ts +21 -0
- package/src/lib/egress.ts +8 -4
- package/src/lib/errors.ts +37 -0
- package/src/lib/ids.ts +11 -0
- package/src/lib/kelpieConfigFile.ts +69 -1
- package/src/modules/agent-tasks/catalog.ts +33 -1
- package/src/modules/agent-tasks/index.ts +1 -0
- package/src/modules/agent-tasks/resolve.ts +11 -0
- package/src/modules/attachedRecords.ts +85 -13
- package/src/modules/auth/index.ts +26 -4
- package/src/modules/auth/repository.ts +36 -4
- package/src/modules/auth/routes.ts +46 -9
- package/src/modules/auth/schema.ts +12 -1
- package/src/modules/auth/service.ts +261 -10
- package/src/modules/auth/session.ts +14 -0
- package/src/modules/companies/repository.ts +1 -0
- package/src/modules/companies/routes.ts +6 -11
- package/src/modules/companies/schema.ts +3 -3
- package/src/modules/companies/service.ts +6 -10
- package/src/modules/core.ts +2 -0
- package/src/modules/custom-fields/repository.ts +2 -0
- package/src/modules/dashboard/index.ts +1 -0
- package/src/modules/dashboard/repository.ts +64 -0
- package/src/modules/dashboard/routes.ts +15 -0
- package/src/modules/dashboard/service.ts +48 -0
- package/src/modules/decisions/repository.ts +2 -0
- package/src/modules/events/attendances.ts +245 -0
- package/src/modules/events/catalog.ts +36 -0
- package/src/modules/events/index.ts +54 -0
- package/src/modules/events/repository.ts +396 -0
- package/src/modules/events/routes.ts +405 -0
- package/src/modules/events/schema.ts +148 -0
- package/src/modules/events/service.ts +443 -0
- package/src/modules/events/tools.ts +118 -0
- package/src/modules/forms/applySubmissionFields.ts +7 -5
- package/src/modules/forms/fillAddresses.ts +185 -0
- package/src/modules/forms/repository.ts +3 -3
- package/src/modules/forms/routes.ts +2 -2
- package/src/modules/forms/schema.ts +2 -2
- package/src/modules/forms/service.ts +2 -2
- package/src/modules/forms/submission.ts +34 -4
- package/src/modules/hiring/roles.ts +2 -1
- package/src/modules/import-export/addresses.ts +150 -0
- package/src/modules/import-export/drafts.ts +7 -9
- package/src/modules/import-export/exportRows.ts +3 -3
- package/src/modules/import-export/plan.ts +30 -2
- package/src/modules/import-export/presets.ts +55 -7
- package/src/modules/import-export/repository.ts +5 -7
- package/src/modules/import-export/writes.ts +8 -4
- package/src/modules/people/repository.ts +1 -0
- package/src/modules/people/routes.ts +6 -6
- package/src/modules/people/schema.ts +7 -5
- package/src/modules/people/service.ts +7 -5
- package/src/modules/plans/repository.ts +2 -2
- package/src/modules/plans/routes.ts +6 -6
- package/src/modules/plans/schema.ts +4 -4
- package/src/modules/plans/service.ts +3 -3
- package/src/modules/plans/tools.ts +2 -2
- package/src/modules/recordTargets.ts +14 -0
- package/src/modules/references.ts +2 -0
- package/src/modules/sample-data/fixture.ts +208 -27
- package/src/modules/sample-data/index.ts +7 -2
- package/src/modules/sample-data/routes.ts +2 -0
- package/src/modules/sample-data/service.ts +166 -49
- package/src/modules/sample-data/tools.ts +4 -3
- package/src/modules/search/index.ts +2 -1
- package/src/modules/search/repository.ts +32 -1
- package/src/modules/search/service.ts +6 -4
- package/src/modules/smtp-email/index.ts +32 -10
- package/src/modules/workspace/repository.ts +18 -0
- package/src/modules/workspace/routes.ts +16 -0
- package/src/modules/workspace/service.ts +59 -14
- package/src/modules/workspace/starters.ts +275 -14
- package/src/modules/workspace/workspaceAccessMiddleware.ts +3 -4
- package/src/runtime/events.ts +2 -0
- package/src/runtime/module.ts +78 -1
- package/src/runtime/registry.ts +176 -8
- package/src/schema/index.ts +1 -0
- package/src/testing/app.ts +14 -1
- package/src/testing/client.ts +11 -1
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
import type { PublicRegion } from '@kelpie/schemas'
|
|
2
|
+
import { publicRegionSchema } from '@kelpie/schemas'
|
|
3
|
+
|
|
1
4
|
import type { KelpieModule } from '../runtime/module.ts'
|
|
2
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ConfigurationError,
|
|
7
|
+
type Environment,
|
|
8
|
+
type KelpieConfig,
|
|
9
|
+
type LogLevel,
|
|
10
|
+
type RuntimeMode,
|
|
11
|
+
type SignupsMode,
|
|
12
|
+
} from './config.ts'
|
|
3
13
|
import type { EmailConfig } from './email.ts'
|
|
14
|
+
import { describeValidationIssue } from './errors.ts'
|
|
4
15
|
import { type ConfigValue, resolveMarkers } from './fromEnv.ts'
|
|
5
16
|
import type { LoggingDestination } from './logger.ts'
|
|
6
17
|
import type { RateLimitConfig } from './rateLimit.ts'
|
|
@@ -35,6 +46,19 @@ export interface KelpieConfigInput {
|
|
|
35
46
|
readonly siteName?: ConfigValue<string | undefined>
|
|
36
47
|
/** Optional; defaults to 0 (no proxy in front). */
|
|
37
48
|
readonly trustedProxyHopCount?: ConfigValue<number>
|
|
49
|
+
/**
|
|
50
|
+
* Whether the instance accepts a brand-new account. Optional; defaults to
|
|
51
|
+
* `'open'`, matching `loadConfig`'s default, so an assembly that never sets
|
|
52
|
+
* this sees no change in behaviour.
|
|
53
|
+
*/
|
|
54
|
+
readonly signups?: ConfigValue<SignupsMode>
|
|
55
|
+
/**
|
|
56
|
+
* Origins a multi-region assembly advertises on signed-out pages. Optional;
|
|
57
|
+
* omitted or empty hides the switcher, matching a self-hosted install.
|
|
58
|
+
* Two or more draws it. Each `id` and `origin` must be unique, and each
|
|
59
|
+
* `origin` must be an absolute `http:` or `https:` origin.
|
|
60
|
+
*/
|
|
61
|
+
readonly regions?: ConfigValue<readonly PublicRegion[]>
|
|
38
62
|
/**
|
|
39
63
|
* The deployment's base URL. Every emailed link (password reset, email
|
|
40
64
|
* verification, invite) is built from it. Required by the workspace and auth
|
|
@@ -164,6 +188,9 @@ export function resolveKelpieConfig(input: KelpieConfigInput, environment: Envir
|
|
|
164
188
|
const rateLimit = buildRateLimitConfig(resolved.rateLimit)
|
|
165
189
|
const env = mergeEnv(environment, resolved.env)
|
|
166
190
|
const secretEncryption = buildSecretEncryptionConfig(resolved.secretEncryption)
|
|
191
|
+
const regions = resolved.regions ?? []
|
|
192
|
+
|
|
193
|
+
problems.push(...describeRegionProblems(regions))
|
|
167
194
|
|
|
168
195
|
if (problems.length > 0) {
|
|
169
196
|
throw new ConfigurationError(problems)
|
|
@@ -186,6 +213,8 @@ export function resolveKelpieConfig(input: KelpieConfigInput, environment: Envir
|
|
|
186
213
|
env,
|
|
187
214
|
appBaseUrl: resolved.appBaseUrl,
|
|
188
215
|
secretEncryption,
|
|
216
|
+
signups: resolved.signups ?? 'open',
|
|
217
|
+
regions,
|
|
189
218
|
}
|
|
190
219
|
}
|
|
191
220
|
|
|
@@ -217,6 +246,8 @@ interface ResolvedInput {
|
|
|
217
246
|
readonly moduleConfigPath?: string | undefined
|
|
218
247
|
readonly siteName?: string | undefined
|
|
219
248
|
readonly trustedProxyHopCount?: number
|
|
249
|
+
readonly signups?: SignupsMode
|
|
250
|
+
readonly regions?: readonly PublicRegion[]
|
|
220
251
|
readonly appBaseUrl?: string
|
|
221
252
|
readonly secretEncryption?: ResolvedSecretEncryption
|
|
222
253
|
readonly email: ResolvedEmail
|
|
@@ -242,6 +273,43 @@ interface ResolvedRateLimit {
|
|
|
242
273
|
readonly api?: { readonly limit?: number; readonly windowSeconds?: number }
|
|
243
274
|
}
|
|
244
275
|
|
|
276
|
+
function describeRegionProblems(regions: readonly unknown[]): string[] {
|
|
277
|
+
const problems: string[] = []
|
|
278
|
+
const ids = new Set<string>()
|
|
279
|
+
const origins = new Set<string>()
|
|
280
|
+
|
|
281
|
+
for (const [index, region] of regions.entries()) {
|
|
282
|
+
const parsed = publicRegionSchema.safeParse(region)
|
|
283
|
+
|
|
284
|
+
if (!parsed.success) {
|
|
285
|
+
for (const issue of parsed.error.issues) {
|
|
286
|
+
problems.push(
|
|
287
|
+
describeValidationIssue({
|
|
288
|
+
path: ['regions', index, ...issue.path],
|
|
289
|
+
message: issue.message,
|
|
290
|
+
}),
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
continue
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (ids.has(parsed.data.id)) {
|
|
298
|
+
problems.push(`regions.${String(index)}.id: duplicate id '${parsed.data.id}'`)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
ids.add(parsed.data.id)
|
|
302
|
+
|
|
303
|
+
if (origins.has(parsed.data.origin)) {
|
|
304
|
+
problems.push(`regions.${String(index)}.origin: duplicate origin '${parsed.data.origin}'`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
origins.add(parsed.data.origin)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return problems
|
|
311
|
+
}
|
|
312
|
+
|
|
245
313
|
function buildEmailConfig(resolved: ResolvedEmail | undefined): EmailConfig {
|
|
246
314
|
// Missing `email.provider` or `email.from` is already recorded by the
|
|
247
315
|
// walker; return a placeholder so `resolveKelpieConfig` reaches its throw
|
|
@@ -128,7 +128,7 @@ Append a Note, update summary only when clearly supported, propose Plan items on
|
|
|
128
128
|
targetTypes: ['company'],
|
|
129
129
|
placement: 'primary',
|
|
130
130
|
handbookSlugs: ['ideal-customer-profile', 'agent-faq'],
|
|
131
|
-
instructions: `Research this Company. Update description, stage, sizeBand, techStack,
|
|
131
|
+
instructions: `Research this Company. Update description, stage, sizeBand, techStack, domain when evidenced, tags, and summary.
|
|
132
132
|
Append a Note listing sources. Prefer notes over guesses for uncertain facts.`,
|
|
133
133
|
}),
|
|
134
134
|
task({
|
|
@@ -623,6 +623,28 @@ Ask before setting stage to passed.`,
|
|
|
623
623
|
instructions: `The human will paste notes after this prompt. Append a Note; propose Plan items; extract Decisions.`,
|
|
624
624
|
}),
|
|
625
625
|
|
|
626
|
+
// Event
|
|
627
|
+
task({
|
|
628
|
+
id: 'event.brief',
|
|
629
|
+
label: 'Event brief',
|
|
630
|
+
description: 'Agenda, who is registered, linked records, open Plans.',
|
|
631
|
+
targetTypes: ['event'],
|
|
632
|
+
placement: 'primary',
|
|
633
|
+
handbookSlugs: ['agent-faq'],
|
|
634
|
+
instructions: `Prepare a brief for this Event: agenda from details, who is registered, linked records, and open Plan items.
|
|
635
|
+
Do not invent attendees. Prefer pinned notes and Decisions.`,
|
|
636
|
+
}),
|
|
637
|
+
task({
|
|
638
|
+
id: 'event.follow_up',
|
|
639
|
+
label: 'Follow up',
|
|
640
|
+
description: 'Draft follow-up from attended vs no-show Attendances.',
|
|
641
|
+
targetTypes: ['event'],
|
|
642
|
+
placement: 'primary',
|
|
643
|
+
handbookSlugs: ['voice-and-tone', 'agent-faq'],
|
|
644
|
+
instructions: `Draft follow-up from this Event's Attendances. Separate attended from no-show.
|
|
645
|
+
Save drafts as Notes. Do not send email.`,
|
|
646
|
+
}),
|
|
647
|
+
|
|
626
648
|
// Candidate
|
|
627
649
|
task({
|
|
628
650
|
id: 'candidate.score',
|
|
@@ -753,6 +775,16 @@ Suggest Draft outreach or Plan items. Do not spam everyone.`,
|
|
|
753
775
|
handbookSlugs: ['how-we-sell', 'agent-faq'],
|
|
754
776
|
instructions: `Propose a week of Plan items across open Deals, Opportunities, Raises, and Partnerships.
|
|
755
777
|
Create Plan items only when clearly useful; otherwise list proposals for confirmation.`,
|
|
778
|
+
}),
|
|
779
|
+
task({
|
|
780
|
+
id: 'workspace.upcoming_events',
|
|
781
|
+
label: 'Upcoming events',
|
|
782
|
+
description: 'Scheduled Events in the next week and their attendee counts.',
|
|
783
|
+
targetTypes: ['workspace'],
|
|
784
|
+
placement: 'primary',
|
|
785
|
+
handbookSlugs: ['agent-faq'],
|
|
786
|
+
instructions: `List scheduled Events in the next week with attendee counts.
|
|
787
|
+
Use GET /v1/dashboard upcoming_events. Suggest follow-up or brief tasks where useful.`,
|
|
756
788
|
}),
|
|
757
789
|
task({
|
|
758
790
|
id: 'workspace.empty_field_sweep',
|
|
@@ -9,6 +9,7 @@ import { companies } from '../companies/schema.ts'
|
|
|
9
9
|
import { deals } from '../deals/schema.ts'
|
|
10
10
|
import { decisions } from '../decisions/schema.ts'
|
|
11
11
|
import { enquiries } from '../enquiries/schema.ts'
|
|
12
|
+
import { attendances } from '../events/schema.ts'
|
|
12
13
|
import { handbookPages } from '../handbook/schema.ts'
|
|
13
14
|
import { candidates, roles } from '../hiring/schema.ts'
|
|
14
15
|
import { notes } from '../notes/schema.ts'
|
|
@@ -443,6 +444,16 @@ async function collectRelated(
|
|
|
443
444
|
),
|
|
444
445
|
}
|
|
445
446
|
|
|
447
|
+
case 'event':
|
|
448
|
+
return {
|
|
449
|
+
person_ids: await relatedFrom(
|
|
450
|
+
db,
|
|
451
|
+
attendances,
|
|
452
|
+
attendances.personId,
|
|
453
|
+
and(eq(attendances.workspaceId, workspaceId), eq(attendances.eventId, targetId)) as SQL,
|
|
454
|
+
),
|
|
455
|
+
}
|
|
456
|
+
|
|
446
457
|
case 'handbook':
|
|
447
458
|
case 'workspace':
|
|
448
459
|
return {}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EVENT_ASSOCIATION_TARGET_TYPES,
|
|
3
|
+
FORM_ATTACH_TARGET_TYPES,
|
|
4
|
+
PIPELINE_KINDS,
|
|
5
|
+
PLAN_ITEM_TARGET_TYPES,
|
|
6
|
+
} from '@kelpie/schemas'
|
|
2
7
|
import type { PipelineKind } from '@kelpie/schemas'
|
|
3
8
|
import { and, eq } from 'drizzle-orm'
|
|
4
9
|
|
|
5
10
|
import type { Queryable } from '../runtime/transaction.ts'
|
|
6
11
|
import * as activityRepository from './activities/repository.ts'
|
|
7
12
|
import * as decisionRepository from './decisions/repository.ts'
|
|
13
|
+
import { attendances, eventAssociations } from './events/schema.ts'
|
|
8
14
|
import { formAttachTargets } from './forms/schema.ts'
|
|
9
15
|
import { candidates } from './hiring/schema.ts'
|
|
10
16
|
import * as listRepository from './lists/repository.ts'
|
|
@@ -20,19 +26,26 @@ import * as planRepository from './plans/repository.ts'
|
|
|
20
26
|
* the target deletes these, in the same transaction, or the rows outlive the
|
|
21
27
|
* record they described and reappear the day an id is reused.
|
|
22
28
|
*
|
|
23
|
-
* `plan_items` attach the same way but their check constraint allows
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* is the same shape: forms pinning submitters into a pre-existing pipeline
|
|
29
|
-
* record shed the mapping when the target goes.
|
|
29
|
+
* `plan_items` attach the same way but their check constraint allows the
|
|
30
|
+
* pipeline types plus Event, so they are removed only for those targets.
|
|
31
|
+
* `person_links` stays pipeline-only. `form_attach_targets` follows the plan
|
|
32
|
+
* set (pipelines plus Event). `event_associations` is Event-only: deleting the
|
|
33
|
+
* other record strips rows that pointed at it.
|
|
30
34
|
*/
|
|
31
35
|
|
|
32
36
|
/** Target types that can own attached records here. */
|
|
33
|
-
export type AttachableTargetType =
|
|
37
|
+
export type AttachableTargetType =
|
|
38
|
+
| 'person'
|
|
39
|
+
| 'company'
|
|
40
|
+
| 'candidate'
|
|
41
|
+
| 'event'
|
|
42
|
+
| 'attendance'
|
|
43
|
+
| PipelineKind
|
|
34
44
|
|
|
35
|
-
const PLAN_TARGET_TYPES: ReadonlySet<string> = new Set(
|
|
45
|
+
const PLAN_TARGET_TYPES: ReadonlySet<string> = new Set(PLAN_ITEM_TARGET_TYPES)
|
|
46
|
+
const LINK_TARGET_TYPES: ReadonlySet<string> = new Set(PIPELINE_KINDS)
|
|
47
|
+
const FORM_ATTACH_TYPES: ReadonlySet<string> = new Set(FORM_ATTACH_TARGET_TYPES)
|
|
48
|
+
const ASSOCIATION_TARGET_TYPES: ReadonlySet<string> = new Set(EVENT_ASSOCIATION_TARGET_TYPES)
|
|
36
49
|
|
|
37
50
|
/**
|
|
38
51
|
* @param db Must be the caller's transaction. Called on its own, this deletes the
|
|
@@ -53,13 +66,13 @@ export async function deleteRecordsAttachedTo(
|
|
|
53
66
|
const plans = PLAN_TARGET_TYPES.has(targetType)
|
|
54
67
|
? await planRepository.deleteForTarget(db, workspaceId, targetType, targetId)
|
|
55
68
|
: 0
|
|
56
|
-
const links =
|
|
69
|
+
const links = LINK_TARGET_TYPES.has(targetType)
|
|
57
70
|
? await personLinks.deleteLinksForTarget(db, workspaceId, {
|
|
58
71
|
targetType: targetType as PipelineKind,
|
|
59
72
|
targetId,
|
|
60
73
|
})
|
|
61
74
|
: 0
|
|
62
|
-
const attachTargets =
|
|
75
|
+
const attachTargets = FORM_ATTACH_TYPES.has(targetType)
|
|
63
76
|
? (
|
|
64
77
|
await db
|
|
65
78
|
.delete(formAttachTargets)
|
|
@@ -79,8 +92,36 @@ export async function deleteRecordsAttachedTo(
|
|
|
79
92
|
targetType,
|
|
80
93
|
targetId,
|
|
81
94
|
)
|
|
95
|
+
const associations = ASSOCIATION_TARGET_TYPES.has(targetType)
|
|
96
|
+
? await deleteEventAssociationsForTarget(db, workspaceId, targetType, targetId)
|
|
97
|
+
: 0
|
|
98
|
+
|
|
99
|
+
return notes + activities + decisions + plans + links + attachTargets + memberships + associations
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Event↔record rows have no FK to the target, so the target's delete must strip
|
|
104
|
+
* them. Event delete itself cascades `event_id`. Role is not an attachable
|
|
105
|
+
* notes target, so its service calls this helper directly.
|
|
106
|
+
*/
|
|
107
|
+
export async function deleteEventAssociationsForTarget(
|
|
108
|
+
db: Queryable,
|
|
109
|
+
workspaceId: string,
|
|
110
|
+
targetType: string,
|
|
111
|
+
targetId: string,
|
|
112
|
+
): Promise<number> {
|
|
113
|
+
const deleted = await db
|
|
114
|
+
.delete(eventAssociations)
|
|
115
|
+
.where(
|
|
116
|
+
and(
|
|
117
|
+
eq(eventAssociations.workspaceId, workspaceId),
|
|
118
|
+
eq(eventAssociations.targetType, targetType),
|
|
119
|
+
eq(eventAssociations.targetId, targetId),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
.returning({ id: eventAssociations.id })
|
|
82
123
|
|
|
83
|
-
return
|
|
124
|
+
return deleted.length
|
|
84
125
|
}
|
|
85
126
|
|
|
86
127
|
/**
|
|
@@ -117,3 +158,34 @@ export async function deleteRecordsAttachedToCandidaciesOf(
|
|
|
117
158
|
|
|
118
159
|
return removed
|
|
119
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The same cleanup for Attendances a person's (or Event's) delete takes with
|
|
164
|
+
* them. `attendances.person_id` / `event_id` cascade, so notes on those rows
|
|
165
|
+
* would otherwise outlive the Attendance.
|
|
166
|
+
*/
|
|
167
|
+
export async function deleteRecordsAttachedToAttendancesOf(
|
|
168
|
+
db: Queryable,
|
|
169
|
+
workspaceId: string,
|
|
170
|
+
filter: { readonly personId: string } | { readonly eventId: string },
|
|
171
|
+
): Promise<number> {
|
|
172
|
+
const held = await db
|
|
173
|
+
.select({ id: attendances.id })
|
|
174
|
+
.from(attendances)
|
|
175
|
+
.where(
|
|
176
|
+
and(
|
|
177
|
+
eq(attendances.workspaceId, workspaceId),
|
|
178
|
+
'personId' in filter
|
|
179
|
+
? eq(attendances.personId, filter.personId)
|
|
180
|
+
: eq(attendances.eventId, filter.eventId),
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
let removed = 0
|
|
185
|
+
|
|
186
|
+
for (const attendance of held) {
|
|
187
|
+
removed += await deleteRecordsAttachedTo(db, workspaceId, 'attendance', attendance.id)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return removed
|
|
191
|
+
}
|
|
@@ -5,6 +5,7 @@ import type { KelpieModule } from '../../runtime/module.ts'
|
|
|
5
5
|
import { mountAuthRoutes } from './routes.ts'
|
|
6
6
|
import * as schema from './schema.ts'
|
|
7
7
|
import { createAuthService } from './service.ts'
|
|
8
|
+
import { describeClient, writeSessionCookie } from './session.ts'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Accounts, sessions, and password recovery.
|
|
@@ -36,8 +37,14 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
|
|
|
36
37
|
createId: context.createId,
|
|
37
38
|
now: context.now,
|
|
38
39
|
appBaseUrl,
|
|
40
|
+
signupsEnabled: context.signupsEnabled ?? true,
|
|
39
41
|
})
|
|
40
42
|
|
|
43
|
+
// `Secure` everywhere except development. A test host reaches the API
|
|
44
|
+
// over http through a test client, which stores the cookie regardless of
|
|
45
|
+
// the flag, so test is not excluded.
|
|
46
|
+
const cookie = { secure: NODE_ENV !== 'development' }
|
|
47
|
+
|
|
41
48
|
context.schema(schema, migrationsDirectory)
|
|
42
49
|
|
|
43
50
|
context.routes((router) => {
|
|
@@ -45,11 +52,26 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
|
|
|
45
52
|
db: context.db,
|
|
46
53
|
now: context.now,
|
|
47
54
|
service,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
cookie,
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// How a module signs a browser in. It verifies an identity its own way
|
|
60
|
+
// and hands it here; everything from the cookie onwards is what a
|
|
61
|
+
// password sign-in does, through the same helper with the same flags.
|
|
62
|
+
context.provideExternalSignIn(async (honoContext, identity) => {
|
|
63
|
+
const issued = await service.completeExternalSignIn({
|
|
64
|
+
...identity,
|
|
65
|
+
...describeClient(honoContext),
|
|
52
66
|
})
|
|
67
|
+
|
|
68
|
+
writeSessionCookie(honoContext, issued.sessionToken, cookie)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
account: issued.account,
|
|
72
|
+
created: issued.created,
|
|
73
|
+
activeWorkspaceId: issued.activeWorkspaceId,
|
|
74
|
+
}
|
|
53
75
|
})
|
|
54
76
|
|
|
55
77
|
return Promise.resolve()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { and, desc, eq, gt, isNull, ne } from 'drizzle-orm'
|
|
1
|
+
import { and, asc, desc, eq, gt, isNull, ne } from 'drizzle-orm'
|
|
2
2
|
|
|
3
3
|
import type { Database } from '../../lib/database.ts'
|
|
4
4
|
import type { Transaction } from '../../runtime/transaction.ts'
|
|
5
|
-
import { workspaceMembers } from '../workspace/schema.ts'
|
|
5
|
+
import { workspaceMembers, workspaces } from '../workspace/schema.ts'
|
|
6
6
|
import {
|
|
7
7
|
emailVerificationTokens,
|
|
8
8
|
passwordResetTokens,
|
|
@@ -48,10 +48,11 @@ export async function insertUser(db: Queryable, values: typeof users.$inferInser
|
|
|
48
48
|
return created
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** `null` clears the password, leaving the account reachable only through a module or a reset. */
|
|
51
52
|
export async function updateUserPassword(
|
|
52
53
|
db: Queryable,
|
|
53
54
|
userId: string,
|
|
54
|
-
passwordHash: string,
|
|
55
|
+
passwordHash: string | null,
|
|
55
56
|
now: Date,
|
|
56
57
|
): Promise<void> {
|
|
57
58
|
await db
|
|
@@ -63,11 +64,15 @@ export async function updateUserPassword(
|
|
|
63
64
|
/**
|
|
64
65
|
* Writes name and email. Not the password: that one has its own function because
|
|
65
66
|
* it also ends sessions, and the two must not be reachable through one call.
|
|
67
|
+
*
|
|
68
|
+
* `emailVerifiedAt` rides along with an email change: the service resets it to
|
|
69
|
+
* null in the same call rather than a second write, so there is no window where
|
|
70
|
+
* the row holds a new address and the old address's verification.
|
|
66
71
|
*/
|
|
67
72
|
export async function updateUserProfile(
|
|
68
73
|
db: Queryable,
|
|
69
74
|
userId: string,
|
|
70
|
-
changes: { readonly name?: string; readonly email?: string },
|
|
75
|
+
changes: { readonly name?: string; readonly email?: string; readonly emailVerifiedAt?: Date | null },
|
|
71
76
|
now: Date,
|
|
72
77
|
): Promise<UserRecord | undefined> {
|
|
73
78
|
const [updated] = await db
|
|
@@ -293,3 +298,30 @@ export async function findFirstMembership(
|
|
|
293
298
|
|
|
294
299
|
return found
|
|
295
300
|
}
|
|
301
|
+
|
|
302
|
+
/** Every workspace this account belongs to, oldest membership first. */
|
|
303
|
+
export interface AccountWorkspaceRecord {
|
|
304
|
+
readonly id: string
|
|
305
|
+
readonly name: string
|
|
306
|
+
readonly slug: string
|
|
307
|
+
readonly timezone: string
|
|
308
|
+
readonly role: string
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function listWorkspacesForUser(
|
|
312
|
+
db: Queryable,
|
|
313
|
+
userId: string,
|
|
314
|
+
): Promise<AccountWorkspaceRecord[]> {
|
|
315
|
+
return db
|
|
316
|
+
.select({
|
|
317
|
+
id: workspaces.id,
|
|
318
|
+
name: workspaces.name,
|
|
319
|
+
slug: workspaces.slug,
|
|
320
|
+
timezone: workspaces.timezone,
|
|
321
|
+
role: workspaceMembers.role,
|
|
322
|
+
})
|
|
323
|
+
.from(workspaceMembers)
|
|
324
|
+
.innerJoin(workspaces, eq(workspaces.id, workspaceMembers.workspaceId))
|
|
325
|
+
.where(eq(workspaceMembers.userId, userId))
|
|
326
|
+
.orderBy(asc(workspaceMembers.joinedAt))
|
|
327
|
+
}
|
|
@@ -10,8 +10,8 @@ import type { SessionActor } from './actor.ts'
|
|
|
10
10
|
import { resolveActorFrom } from './credentials.ts'
|
|
11
11
|
import type { CredentialDependencies } from './credentials.ts'
|
|
12
12
|
import type { PreferenceValues } from './preferences.ts'
|
|
13
|
-
import type { AccountView, AuthService, IssuedSession, SessionView } from './service.ts'
|
|
14
|
-
import { clearSessionCookie, writeSessionCookie } from './session.ts'
|
|
13
|
+
import type { AccountView, AuthService, IssuedSession, MeView, SessionView } from './service.ts'
|
|
14
|
+
import { clearSessionCookie, describeClient, writeSessionCookie } from './session.ts'
|
|
15
15
|
import type { SessionCookieOptions } from './session.ts'
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -44,6 +44,10 @@ const changePasswordBody = z.object({
|
|
|
44
44
|
new_password: z.string().min(1),
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
+
const switchWorkspaceBodySchema = z.object({
|
|
48
|
+
workspace_id: z.string().min(1),
|
|
49
|
+
})
|
|
50
|
+
|
|
47
51
|
const verifyEmailConfirmBody = z.object({
|
|
48
52
|
token: z.string().min(1),
|
|
49
53
|
})
|
|
@@ -108,13 +112,6 @@ async function readBody<T>(context: Context, schema: z.ZodType<T>): Promise<T> {
|
|
|
108
112
|
return parsed.data
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
/** The device and location shown on the Security page come from the request itself. */
|
|
112
|
-
function describeClient(context: Context): { device?: string; location?: string } {
|
|
113
|
-
const userAgent = context.req.header('User-Agent')
|
|
114
|
-
|
|
115
|
-
return userAgent === undefined ? {} : { device: userAgent }
|
|
116
|
-
}
|
|
117
|
-
|
|
118
115
|
function accountResponse(issued: IssuedSession): Record<string, unknown> {
|
|
119
116
|
return {
|
|
120
117
|
account: accountBody(issued.account),
|
|
@@ -142,6 +139,32 @@ function preferencesResponse(preferences: PreferenceValues): Record<string, unkn
|
|
|
142
139
|
}
|
|
143
140
|
}
|
|
144
141
|
|
|
142
|
+
function sessionMeResponse(session: MeView): Record<string, unknown> {
|
|
143
|
+
return {
|
|
144
|
+
user_id: session.userId,
|
|
145
|
+
session_id: session.sessionId,
|
|
146
|
+
workspace_id: session.workspaceId,
|
|
147
|
+
role: session.role,
|
|
148
|
+
email_verified: session.emailVerified,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function accountWorkspaceResponse(workspace: {
|
|
153
|
+
readonly id: string
|
|
154
|
+
readonly name: string
|
|
155
|
+
readonly slug: string
|
|
156
|
+
readonly timezone: string
|
|
157
|
+
readonly role: string
|
|
158
|
+
}): Record<string, unknown> {
|
|
159
|
+
return {
|
|
160
|
+
id: workspace.id,
|
|
161
|
+
name: workspace.name,
|
|
162
|
+
slug: workspace.slug,
|
|
163
|
+
timezone: workspace.timezone,
|
|
164
|
+
role: workspace.role,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
145
168
|
function sessionResponse(session: SessionView): Record<string, unknown> {
|
|
146
169
|
return {
|
|
147
170
|
id: session.id,
|
|
@@ -149,6 +172,7 @@ function sessionResponse(session: SessionView): Record<string, unknown> {
|
|
|
149
172
|
location: session.location,
|
|
150
173
|
last_active_at: session.lastActiveAt.toISOString(),
|
|
151
174
|
current: session.current,
|
|
175
|
+
signed_in_via: session.signedInVia,
|
|
152
176
|
}
|
|
153
177
|
}
|
|
154
178
|
|
|
@@ -200,6 +224,19 @@ export function mountAuthRoutes(router: Hono, dependencies: AuthRoutesDependenci
|
|
|
200
224
|
})
|
|
201
225
|
})
|
|
202
226
|
|
|
227
|
+
router.get('/auth/workspaces', async (context) => {
|
|
228
|
+
const workspaces = await dependencies.service.listWorkspaces(await requireActor(context))
|
|
229
|
+
|
|
230
|
+
return context.json({ data: workspaces.map(accountWorkspaceResponse), next_cursor: null })
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
router.post('/auth/workspace', async (context) => {
|
|
234
|
+
const body = await readBody(context, switchWorkspaceBodySchema)
|
|
235
|
+
const session = await dependencies.service.switchWorkspace(await requireActor(context), body.workspace_id)
|
|
236
|
+
|
|
237
|
+
return context.json(sessionMeResponse(session))
|
|
238
|
+
})
|
|
239
|
+
|
|
203
240
|
/**
|
|
204
241
|
* `/account` rather than `/auth/account`: `/auth/*` is how a browser gets a
|
|
205
242
|
* session, and this is the person that session belongs to. The auth module
|
|
@@ -18,7 +18,12 @@ export const users = pgTable('users', {
|
|
|
18
18
|
id: primaryId(),
|
|
19
19
|
email: citext('email').notNull().unique(),
|
|
20
20
|
name: text('name').notNull(),
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Null for an account that has only ever signed in through an identity
|
|
23
|
+
* module. Password sign-in is refused while it is null; a password is set
|
|
24
|
+
* through the reset flow, whose emailed link proves control of the address.
|
|
25
|
+
*/
|
|
26
|
+
passwordHash: text('password_hash'),
|
|
22
27
|
/** Null until the address is verified, either by confirming a token or by accepting a workspace invite. */
|
|
23
28
|
emailVerifiedAt: moment('email_verified_at'),
|
|
24
29
|
createdAt: createdAt(),
|
|
@@ -34,6 +39,12 @@ export const sessions = pgTable('sessions', {
|
|
|
34
39
|
tokenHash: text('token_hash').notNull().unique(),
|
|
35
40
|
device: text('device'),
|
|
36
41
|
location: text('location'),
|
|
42
|
+
/**
|
|
43
|
+
* Which module completed this sign-in (`sign-in:google`), null for a password
|
|
44
|
+
* one. The Security page lists sessions, and a person should be able to tell
|
|
45
|
+
* the two apart.
|
|
46
|
+
*/
|
|
47
|
+
signedInVia: text('signed_in_via'),
|
|
37
48
|
lastActiveAt: moment('last_active_at').notNull().defaultNow(),
|
|
38
49
|
expiresAt: moment('expires_at').notNull(),
|
|
39
50
|
createdAt: createdAt(),
|