@kelpie/server 0.12.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/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 +3 -3
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +13 -5
- 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 +1 -0
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/repository.d.ts +14 -0
- package/dist/modules/auth/repository.d.ts.map +1 -1
- package/dist/modules/auth/repository.js +20 -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 +30 -0
- package/dist/modules/auth/routes.js.map +1 -1
- package/dist/modules/auth/service.d.ts +28 -0
- package/dist/modules/auth/service.d.ts.map +1 -1
- package/dist/modules/auth/service.js +72 -4
- package/dist/modules/auth/service.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 +8 -0
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +102 -7
- 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/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 +21 -0
- package/package.json +3 -3
- package/src/app.ts +25 -1
- package/src/boot.ts +1 -0
- 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 +16 -9
- 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 +1 -0
- package/src/modules/auth/repository.ts +34 -3
- package/src/modules/auth/routes.ts +44 -1
- package/src/modules/auth/service.ts +118 -4
- 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 +8 -0
- package/src/runtime/registry.ts +122 -8
- package/src/schema/index.ts +1 -0
- package/src/testing/app.ts +14 -1
- package/src/testing/client.ts +11 -1
|
@@ -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
|
+
}
|
|
@@ -37,6 +37,7 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
|
|
|
37
37
|
createId: context.createId,
|
|
38
38
|
now: context.now,
|
|
39
39
|
appBaseUrl,
|
|
40
|
+
signupsEnabled: context.signupsEnabled ?? true,
|
|
40
41
|
})
|
|
41
42
|
|
|
42
43
|
// `Secure` everywhere except development. A test host reaches the API
|
|
@@ -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,
|
|
@@ -64,11 +64,15 @@ export async function updateUserPassword(
|
|
|
64
64
|
/**
|
|
65
65
|
* Writes name and email. Not the password: that one has its own function because
|
|
66
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.
|
|
67
71
|
*/
|
|
68
72
|
export async function updateUserProfile(
|
|
69
73
|
db: Queryable,
|
|
70
74
|
userId: string,
|
|
71
|
-
changes: { readonly name?: string; readonly email?: string },
|
|
75
|
+
changes: { readonly name?: string; readonly email?: string; readonly emailVerifiedAt?: Date | null },
|
|
72
76
|
now: Date,
|
|
73
77
|
): Promise<UserRecord | undefined> {
|
|
74
78
|
const [updated] = await db
|
|
@@ -294,3 +298,30 @@ export async function findFirstMembership(
|
|
|
294
298
|
|
|
295
299
|
return found
|
|
296
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,7 +10,7 @@ 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'
|
|
13
|
+
import type { AccountView, AuthService, IssuedSession, MeView, SessionView } from './service.ts'
|
|
14
14
|
import { clearSessionCookie, describeClient, writeSessionCookie } from './session.ts'
|
|
15
15
|
import type { SessionCookieOptions } from './session.ts'
|
|
16
16
|
|
|
@@ -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
|
})
|
|
@@ -135,6 +139,32 @@ function preferencesResponse(preferences: PreferenceValues): Record<string, unkn
|
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
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
|
+
|
|
138
168
|
function sessionResponse(session: SessionView): Record<string, unknown> {
|
|
139
169
|
return {
|
|
140
170
|
id: session.id,
|
|
@@ -194,6 +224,19 @@ export function mountAuthRoutes(router: Hono, dependencies: AuthRoutesDependenci
|
|
|
194
224
|
})
|
|
195
225
|
})
|
|
196
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
|
+
|
|
197
240
|
/**
|
|
198
241
|
* `/account` rather than `/auth/account`: `/auth/*` is how a browser gets a
|
|
199
242
|
* session, and this is the person that session belongs to. The auth module
|
|
@@ -9,6 +9,8 @@ import { MINIMUM_PASSWORD_LENGTH, hashPassword, verifyPassword } from '../../lib
|
|
|
9
9
|
import { generateToken, hashToken } from '../../lib/tokens.ts'
|
|
10
10
|
import type { VerifiedIdentity } from '../../runtime/module.ts'
|
|
11
11
|
import type { TransactionScope } from '../../runtime/transaction.ts'
|
|
12
|
+
import type { MemberRole } from '../workspace/roles.ts'
|
|
13
|
+
import { parseMemberRole } from '../workspace/roles.ts'
|
|
12
14
|
import type { SessionActor } from './actor.ts'
|
|
13
15
|
import { DEFAULT_PREFERENCES, applyPreferenceChanges } from './preferences.ts'
|
|
14
16
|
import type { PreferenceChanges, PreferenceValues } from './preferences.ts'
|
|
@@ -36,6 +38,13 @@ export interface AuthDependencies {
|
|
|
36
38
|
readonly now: () => Date
|
|
37
39
|
/** The deployment's own base URL. Every emailed link is built from it. */
|
|
38
40
|
readonly appBaseUrl: string
|
|
41
|
+
/**
|
|
42
|
+
* Whether a brand-new account may be created, through `signUp` or
|
|
43
|
+
* `completeExternalSignIn` provisioning. Joining an existing workspace via
|
|
44
|
+
* invite, and workspace creation by an already-verified account, are
|
|
45
|
+
* unaffected either way — this only gates the account-creation step itself.
|
|
46
|
+
*/
|
|
47
|
+
readonly signupsEnabled: boolean
|
|
39
48
|
/** Injected only so tests can pin tokens. Production uses the crypto default. */
|
|
40
49
|
readonly newToken?: () => string
|
|
41
50
|
}
|
|
@@ -70,6 +79,24 @@ export interface SessionView {
|
|
|
70
79
|
readonly signedInVia: string | null
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
/** What `GET /v1/auth/me` and `POST /v1/auth/workspace` answer with. */
|
|
83
|
+
export interface MeView {
|
|
84
|
+
readonly userId: string
|
|
85
|
+
readonly sessionId: string
|
|
86
|
+
readonly workspaceId: string | null
|
|
87
|
+
readonly role: MemberRole | null
|
|
88
|
+
readonly emailVerified: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** One membership on `GET /v1/auth/workspaces`. */
|
|
92
|
+
export interface AccountWorkspaceView {
|
|
93
|
+
readonly id: string
|
|
94
|
+
readonly name: string
|
|
95
|
+
readonly slug: string
|
|
96
|
+
readonly timezone: string
|
|
97
|
+
readonly role: MemberRole
|
|
98
|
+
}
|
|
99
|
+
|
|
73
100
|
export interface SignUpInput {
|
|
74
101
|
readonly email: string
|
|
75
102
|
readonly name: string
|
|
@@ -166,6 +193,10 @@ export interface AuthService {
|
|
|
166
193
|
updatePreferences(actor: SessionActor, changes: PreferenceChanges): Promise<PreferenceValues>
|
|
167
194
|
listSessions(actor: SessionActor): Promise<readonly SessionView[]>
|
|
168
195
|
revokeSession(actor: SessionActor, sessionId: string): Promise<void>
|
|
196
|
+
/** Workspaces this account belongs to. Oldest membership first. */
|
|
197
|
+
listWorkspaces(actor: SessionActor): Promise<readonly AccountWorkspaceView[]>
|
|
198
|
+
/** Moves this session into a membership. Answers the same shape as `GET /v1/auth/me`. */
|
|
199
|
+
switchWorkspace(actor: SessionActor, workspaceId: string): Promise<MeView>
|
|
169
200
|
/** Resolves whether or not the address is registered. */
|
|
170
201
|
requestPasswordReset(email: string): Promise<void>
|
|
171
202
|
confirmPasswordReset(token: string, newPassword: string): Promise<void>
|
|
@@ -250,6 +281,10 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
250
281
|
return {
|
|
251
282
|
/** Creates the account only. The first workspace comes from onboarding. */
|
|
252
283
|
async signUp(input: SignUpInput): Promise<IssuedSession> {
|
|
284
|
+
if (!dependencies.signupsEnabled) {
|
|
285
|
+
throw new AppError('forbidden', 'Signups are closed on this instance')
|
|
286
|
+
}
|
|
287
|
+
|
|
253
288
|
if (input.password.length < MINIMUM_PASSWORD_LENGTH) {
|
|
254
289
|
throw AppError.validationFailed('Password is too short', [
|
|
255
290
|
{ field: 'password', message: `Must be at least ${MINIMUM_PASSWORD_LENGTH} characters` },
|
|
@@ -372,6 +407,12 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
372
407
|
return dependencies.transaction(({ tx }) => link(tx, existing))
|
|
373
408
|
}
|
|
374
409
|
|
|
410
|
+
// The instance-level gate applies before the module's own policy: a
|
|
411
|
+
// module that would provision does not get to override it.
|
|
412
|
+
if (!dependencies.signupsEnabled) {
|
|
413
|
+
throw new ExternalSignInError('signups_closed')
|
|
414
|
+
}
|
|
415
|
+
|
|
375
416
|
if (input.provision === 'refuse') {
|
|
376
417
|
throw new ExternalSignInError('unknown_identity')
|
|
377
418
|
}
|
|
@@ -426,15 +467,25 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
426
467
|
* password reset. The current password must verify, every other session
|
|
427
468
|
* ends, and the address being replaced is told, in case the caller was not
|
|
428
469
|
* its owner.
|
|
470
|
+
*
|
|
471
|
+
* A real change of address also resets verification and sends a fresh
|
|
472
|
+
* token to the new one. Verifying the old address proves nothing about the
|
|
473
|
+
* new one, and leaving the flag set would let anyone who can rename an
|
|
474
|
+
* unregistered account onto a given address inherit "verified" for free —
|
|
475
|
+
* exactly the mailbox-ownership proof signup's own token exists to require.
|
|
476
|
+
* Submitting the address already on file is not a change and resets nothing.
|
|
429
477
|
*/
|
|
430
478
|
async updateAccount(actor: SessionActor, changes: UpdateAccountChanges): Promise<AccountView> {
|
|
431
479
|
const user = await requireUser(actor.userId)
|
|
432
480
|
|
|
481
|
+
const nextEmail =
|
|
482
|
+
changes.email === undefined ? undefined : requireText(normaliseEmail(changes.email), 'email')
|
|
483
|
+
const emailChanging = nextEmail !== undefined && nextEmail !== user.email
|
|
484
|
+
|
|
433
485
|
const values = {
|
|
434
486
|
...(changes.name === undefined ? {} : { name: requireText(changes.name, 'name') }),
|
|
435
|
-
...(
|
|
436
|
-
|
|
437
|
-
: { email: requireText(normaliseEmail(changes.email), 'email') }),
|
|
487
|
+
...(nextEmail === undefined ? {} : { email: nextEmail }),
|
|
488
|
+
...(emailChanging ? { emailVerifiedAt: null } : {}),
|
|
438
489
|
}
|
|
439
490
|
|
|
440
491
|
// A passwordless account cannot prove itself this way. It sets a password
|
|
@@ -449,10 +500,12 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
449
500
|
}
|
|
450
501
|
|
|
451
502
|
const previousEmail = user.email
|
|
503
|
+
const now = dependencies.now()
|
|
504
|
+
const verificationToken = emailChanging ? newToken() : undefined
|
|
452
505
|
|
|
453
506
|
try {
|
|
454
507
|
const updated = await dependencies.transaction(async ({ tx }) => {
|
|
455
|
-
const saved = await repository.updateUserProfile(tx, actor.userId, values,
|
|
508
|
+
const saved = await repository.updateUserProfile(tx, actor.userId, values, now)
|
|
456
509
|
|
|
457
510
|
if (saved === undefined) {
|
|
458
511
|
throw AppError.unauthorized('This session no longer belongs to an account')
|
|
@@ -462,6 +515,15 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
462
515
|
await repository.deleteOtherSessionsForUser(tx, actor.userId, actor.sessionId)
|
|
463
516
|
}
|
|
464
517
|
|
|
518
|
+
if (verificationToken !== undefined) {
|
|
519
|
+
await repository.insertEmailVerificationToken(tx, {
|
|
520
|
+
id: dependencies.createId('emailVerificationToken'),
|
|
521
|
+
userId: actor.userId,
|
|
522
|
+
tokenHash: hashToken(verificationToken),
|
|
523
|
+
expiresAt: new Date(now.getTime() + EMAIL_VERIFICATION_TOKEN_LIFETIME_MS),
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
|
|
465
527
|
return saved
|
|
466
528
|
})
|
|
467
529
|
|
|
@@ -481,6 +543,13 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
481
543
|
})
|
|
482
544
|
}
|
|
483
545
|
|
|
546
|
+
// Sent after commit, same as signup: a rolled-back change never emails
|
|
547
|
+
// a token, and this token is only ever minted alongside a committed
|
|
548
|
+
// `emailVerifiedAt: null` write.
|
|
549
|
+
if (verificationToken !== undefined) {
|
|
550
|
+
await sendVerificationEmail(updated.email, verificationToken)
|
|
551
|
+
}
|
|
552
|
+
|
|
484
553
|
return toAccountView(updated)
|
|
485
554
|
} catch (error: unknown) {
|
|
486
555
|
if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
|
|
@@ -551,6 +620,51 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
551
620
|
}
|
|
552
621
|
},
|
|
553
622
|
|
|
623
|
+
async listWorkspaces(actor: SessionActor): Promise<readonly AccountWorkspaceView[]> {
|
|
624
|
+
const rows = await repository.listWorkspacesForUser(dependencies.db, actor.userId)
|
|
625
|
+
|
|
626
|
+
return rows.map((row) => {
|
|
627
|
+
const role = parseMemberRole(row.role)
|
|
628
|
+
|
|
629
|
+
if (role === undefined) {
|
|
630
|
+
throw new Error(`workspace_members.role holds "${row.role}", which its check constraint forbids`)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return {
|
|
634
|
+
id: row.id,
|
|
635
|
+
name: row.name,
|
|
636
|
+
slug: row.slug,
|
|
637
|
+
timezone: row.timezone,
|
|
638
|
+
role,
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
async switchWorkspace(actor: SessionActor, workspaceId: string): Promise<MeView> {
|
|
644
|
+
const membership = await repository.findMembership(dependencies.db, workspaceId, actor.userId)
|
|
645
|
+
|
|
646
|
+
if (membership === undefined) {
|
|
647
|
+
throw AppError.notFound('Workspace not found')
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const role = parseMemberRole(membership.role)
|
|
651
|
+
|
|
652
|
+
if (role === undefined) {
|
|
653
|
+
throw new Error(`workspace_members.role holds "${membership.role}", which its check constraint forbids`)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
await repository.setActiveWorkspace(dependencies.db, actor.sessionId, workspaceId)
|
|
657
|
+
const user = await requireUser(actor.userId)
|
|
658
|
+
|
|
659
|
+
return {
|
|
660
|
+
userId: actor.userId,
|
|
661
|
+
sessionId: actor.sessionId,
|
|
662
|
+
workspaceId,
|
|
663
|
+
role,
|
|
664
|
+
emailVerified: user.emailVerifiedAt !== null,
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
|
|
554
668
|
/**
|
|
555
669
|
* Always resolves, whether or not the address is registered. Telling a
|
|
556
670
|
* stranger which addresses have accounts is the whole attack.
|
|
@@ -58,6 +58,7 @@ function matchesTerm(term: string): SQL | undefined {
|
|
|
58
58
|
ilike(companies.summary, pattern),
|
|
59
59
|
ilike(companies.accountType, pattern),
|
|
60
60
|
arrayContainsPattern(companies.tags, pattern),
|
|
61
|
+
sql`kelpie_addresses_search_text(${companies.addresses}) ilike ${pattern}`,
|
|
61
62
|
)
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { customFieldsPatchShape } from '@kelpie/schemas'
|
|
1
|
+
import { addressBody, companyAddressesSchema, customFieldsPatchShape } from '@kelpie/schemas'
|
|
2
2
|
import type { Context, Hono } from 'hono'
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
|
|
@@ -25,8 +25,7 @@ const companyShape = {
|
|
|
25
25
|
description: z.string(),
|
|
26
26
|
stage: z.enum(COMPANY_STAGES),
|
|
27
27
|
size_band: z.enum(SIZE_BANDS),
|
|
28
|
-
|
|
29
|
-
website: z.string().nullable(),
|
|
28
|
+
addresses: companyAddressesSchema,
|
|
30
29
|
account_type: z.enum(ACCOUNT_TYPES),
|
|
31
30
|
icp_fit: z.enum(ICP_FITS),
|
|
32
31
|
tech_stack: z.array(z.string().min(1)),
|
|
@@ -48,8 +47,7 @@ export const createBody = z.strictObject({
|
|
|
48
47
|
description: companyShape.description.default(''),
|
|
49
48
|
stage: companyShape.stage.default('startup'),
|
|
50
49
|
size_band: companyShape.size_band.default('1-10'),
|
|
51
|
-
|
|
52
|
-
website: companyShape.website.default(null),
|
|
50
|
+
addresses: companyShape.addresses.default([]),
|
|
53
51
|
account_type: companyShape.account_type.default('prospect'),
|
|
54
52
|
icp_fit: companyShape.icp_fit.default('unknown'),
|
|
55
53
|
tech_stack: companyShape.tech_stack.default([]),
|
|
@@ -73,8 +71,7 @@ export function toCreateInput(body: z.infer<typeof createBody>): CreateCompanyIn
|
|
|
73
71
|
description: body.description,
|
|
74
72
|
stage: body.stage,
|
|
75
73
|
sizeBand: body.size_band,
|
|
76
|
-
|
|
77
|
-
website: body.website,
|
|
74
|
+
addresses: body.addresses,
|
|
78
75
|
accountType: body.account_type,
|
|
79
76
|
icpFit: body.icp_fit,
|
|
80
77
|
techStack: body.tech_stack,
|
|
@@ -93,8 +90,7 @@ export function toUpdateInput(body: z.infer<typeof updateBody>): UpdateCompanyIn
|
|
|
93
90
|
...(body.description === undefined ? {} : { description: body.description }),
|
|
94
91
|
...(body.stage === undefined ? {} : { stage: body.stage }),
|
|
95
92
|
...(body.size_band === undefined ? {} : { sizeBand: body.size_band }),
|
|
96
|
-
...(body.
|
|
97
|
-
...(body.website === undefined ? {} : { website: body.website }),
|
|
93
|
+
...(body.addresses === undefined ? {} : { addresses: body.addresses }),
|
|
98
94
|
...(body.account_type === undefined ? {} : { accountType: body.account_type }),
|
|
99
95
|
...(body.icp_fit === undefined ? {} : { icpFit: body.icp_fit }),
|
|
100
96
|
...(body.tech_stack === undefined ? {} : { techStack: body.tech_stack }),
|
|
@@ -114,8 +110,7 @@ export function companyResponse(company: CompanyView): Record<string, unknown> {
|
|
|
114
110
|
description: company.description,
|
|
115
111
|
stage: company.stage,
|
|
116
112
|
size_band: company.sizeBand,
|
|
117
|
-
|
|
118
|
-
website: company.website,
|
|
113
|
+
addresses: company.addresses.map(addressBody),
|
|
119
114
|
account_type: company.accountType,
|
|
120
115
|
icp_fit: company.icpFit,
|
|
121
116
|
tech_stack: company.techStack,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ACCOUNT_TYPES, COMPANY_STAGES, ICP_FITS, SIZE_BANDS } from '@kelpie/schemas'
|
|
2
|
-
import type { CustomFieldValue } from '@kelpie/schemas'
|
|
2
|
+
import type { CompanyAddress, CustomFieldValue } from '@kelpie/schemas'
|
|
3
3
|
import { boolean, index, jsonb, pgTable, text, unique } from 'drizzle-orm/pg-core'
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -36,8 +36,7 @@ export const companies = pgTable(
|
|
|
36
36
|
description: text('description').notNull().default(''),
|
|
37
37
|
stage: text('stage').notNull(),
|
|
38
38
|
sizeBand: text('size_band').notNull(),
|
|
39
|
-
|
|
40
|
-
website: text('website'),
|
|
39
|
+
addresses: jsonb('addresses').$type<readonly CompanyAddress[]>().notNull().default([]),
|
|
41
40
|
accountType: text('account_type').notNull(),
|
|
42
41
|
icpFit: text('icp_fit').notNull(),
|
|
43
42
|
techStack: text('tech_stack').array().notNull().default([]),
|
|
@@ -62,6 +61,7 @@ export const companies = pgTable(
|
|
|
62
61
|
{ column: companies.summary, weight: 'B' },
|
|
63
62
|
{ column: companies.tags, weight: 'C', array: true },
|
|
64
63
|
{ column: companies.techStack, weight: 'C', array: true },
|
|
64
|
+
{ column: companies.addresses, weight: 'C', addresses: true },
|
|
65
65
|
]),
|
|
66
66
|
},
|
|
67
67
|
(table) => [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CustomFieldWireValue } from '@kelpie/schemas'
|
|
1
|
+
import type { CompanyAddress, CustomFieldWireValue } from '@kelpie/schemas'
|
|
2
2
|
|
|
3
3
|
import { changedKeys } from '../../lib/changes.ts'
|
|
4
4
|
import { UNIQUE_VIOLATION, isReferenceViolation, postgresErrorCode } from '../../lib/database.ts'
|
|
@@ -49,8 +49,7 @@ const COMPANY_FIELD_LABELS: FieldLabels = {
|
|
|
49
49
|
description: 'Description',
|
|
50
50
|
stage: 'Stage',
|
|
51
51
|
sizeBand: 'Size',
|
|
52
|
-
|
|
53
|
-
website: 'Website',
|
|
52
|
+
addresses: 'Addresses',
|
|
54
53
|
accountType: 'Account type',
|
|
55
54
|
icpFit: 'ICP fit',
|
|
56
55
|
techStack: 'Tech stack',
|
|
@@ -69,8 +68,7 @@ export interface CreateCompanyInput {
|
|
|
69
68
|
readonly description: string
|
|
70
69
|
readonly stage: CompanyStage
|
|
71
70
|
readonly sizeBand: SizeBand
|
|
72
|
-
readonly
|
|
73
|
-
readonly website: string | null
|
|
71
|
+
readonly addresses: readonly CompanyAddress[]
|
|
74
72
|
readonly accountType: AccountType
|
|
75
73
|
readonly icpFit: IcpFit
|
|
76
74
|
readonly techStack: readonly string[]
|
|
@@ -102,7 +100,7 @@ function toView(record: CompanyRecord): CompanyView {
|
|
|
102
100
|
* The stored form of what the caller sent. A domain arrives however someone
|
|
103
101
|
* pasted it and is reduced to a host; blank becomes null, because the domain is
|
|
104
102
|
* unique per workspace and a stored `''` would make the second company without a
|
|
105
|
-
*
|
|
103
|
+
* domain a 409.
|
|
106
104
|
*/
|
|
107
105
|
function toStoredColumns(input: UpdateCompanyInput): Partial<repository.CompanyColumns> {
|
|
108
106
|
return {
|
|
@@ -114,8 +112,7 @@ function toStoredColumns(input: UpdateCompanyInput): Partial<repository.CompanyC
|
|
|
114
112
|
...(input.description === undefined ? {} : { description: input.description }),
|
|
115
113
|
...(input.stage === undefined ? {} : { stage: input.stage }),
|
|
116
114
|
...(input.sizeBand === undefined ? {} : { sizeBand: input.sizeBand }),
|
|
117
|
-
...(input.
|
|
118
|
-
...(input.website === undefined ? {} : { website: input.website }),
|
|
115
|
+
...(input.addresses === undefined ? {} : { addresses: input.addresses }),
|
|
119
116
|
...(input.accountType === undefined ? {} : { accountType: input.accountType }),
|
|
120
117
|
...(input.icpFit === undefined ? {} : { icpFit: input.icpFit }),
|
|
121
118
|
...(input.techStack === undefined ? {} : { techStack: [...input.techStack] }),
|
|
@@ -180,8 +177,7 @@ export function createCompaniesService(dependencies: CompaniesDependencies): Com
|
|
|
180
177
|
description: input.description,
|
|
181
178
|
stage: input.stage,
|
|
182
179
|
sizeBand: input.sizeBand,
|
|
183
|
-
|
|
184
|
-
website: input.website,
|
|
180
|
+
addresses: input.addresses,
|
|
185
181
|
accountType: input.accountType,
|
|
186
182
|
icpFit: input.icpFit,
|
|
187
183
|
techStack: [...input.techStack],
|
package/src/modules/core.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { createDealsModule } from './deals/index.ts'
|
|
|
13
13
|
import { createDecisionsModule } from './decisions/index.ts'
|
|
14
14
|
import { createEmailDomainLinkerModule } from './email-domain-linker/index.ts'
|
|
15
15
|
import { createEnquiriesModule } from './enquiries/index.ts'
|
|
16
|
+
import { createEventsModule } from './events/index.ts'
|
|
16
17
|
import { createFormsModule } from './forms/index.ts'
|
|
17
18
|
import { createHandbookModule } from './handbook/index.ts'
|
|
18
19
|
import { createHiringModule } from './hiring/index.ts'
|
|
@@ -68,6 +69,7 @@ export const coreModules: readonly KelpieModule[] = [
|
|
|
68
69
|
createPartnershipsModule(coreMigrationsDirectory),
|
|
69
70
|
createRaisesModule(coreMigrationsDirectory),
|
|
70
71
|
createHiringModule(coreMigrationsDirectory),
|
|
72
|
+
createEventsModule(coreMigrationsDirectory),
|
|
71
73
|
createPlansModule(coreMigrationsDirectory),
|
|
72
74
|
createDecisionsModule(coreMigrationsDirectory),
|
|
73
75
|
createListsModule(coreMigrationsDirectory),
|
|
@@ -8,6 +8,7 @@ import type { Queryable } from '../../runtime/transaction.ts'
|
|
|
8
8
|
import { companies } from '../companies/schema.ts'
|
|
9
9
|
import { deals } from '../deals/schema.ts'
|
|
10
10
|
import { enquiries } from '../enquiries/schema.ts'
|
|
11
|
+
import { events } from '../events/schema.ts'
|
|
11
12
|
import { opportunities } from '../opportunities/schema.ts'
|
|
12
13
|
import { partnerships } from '../partnerships/schema.ts'
|
|
13
14
|
import { people } from '../people/schema.ts'
|
|
@@ -178,6 +179,7 @@ const TABLES = {
|
|
|
178
179
|
partnership: partnerships,
|
|
179
180
|
raise: raises,
|
|
180
181
|
enquiry: enquiries,
|
|
182
|
+
event: events,
|
|
181
183
|
} as const
|
|
182
184
|
|
|
183
185
|
/**
|