@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,28 +1,31 @@
|
|
|
1
|
-
import { and,
|
|
1
|
+
import { and, eq, inArray } from 'drizzle-orm'
|
|
2
2
|
|
|
3
3
|
import { toEventActor } from '../../lib/actor.ts'
|
|
4
|
+
import { UNIQUE_VIOLATION, postgresErrorCode } from '../../lib/database.ts'
|
|
4
5
|
import type { Database } from '../../lib/database.ts'
|
|
5
6
|
import { AppError } from '../../lib/errors.ts'
|
|
6
7
|
import type { IdFactory } from '../../lib/ids.ts'
|
|
8
|
+
import type { EntitlementRegistry } from '../../runtime/entitlements.ts'
|
|
9
|
+
import { moduleCapabilityName } from '../../runtime/moduleConfig.ts'
|
|
7
10
|
import type { TransactionScope } from '../../runtime/transaction.ts'
|
|
8
11
|
import type { Actor } from '../auth/actor.ts'
|
|
9
12
|
import { requireWorkspaceId } from '../auth/actor.ts'
|
|
10
13
|
import * as authRepository from '../auth/repository.ts'
|
|
11
14
|
import { companies } from '../companies/schema.ts'
|
|
12
15
|
import { deals } from '../deals/schema.ts'
|
|
13
|
-
import { candidates, roles } from '../hiring/schema.ts'
|
|
14
|
-
import { notes } from '../notes/schema.ts'
|
|
15
16
|
import { enquiries } from '../enquiries/schema.ts'
|
|
17
|
+
import { attendances, eventAssociations, events } from '../events/schema.ts'
|
|
18
|
+
import { candidates, roles } from '../hiring/schema.ts'
|
|
16
19
|
import { opportunities } from '../opportunities/schema.ts'
|
|
17
20
|
import { partnerships } from '../partnerships/schema.ts'
|
|
18
21
|
import { people, personLinks } from '../people/schema.ts'
|
|
22
|
+
import { notes } from '../notes/schema.ts'
|
|
19
23
|
import { pipelineStages } from '../pipelines/schema.ts'
|
|
20
24
|
import { planItems } from '../plans/schema.ts'
|
|
21
25
|
import { positions } from '../positions/schema.ts'
|
|
22
26
|
import { raises } from '../raises/schema.ts'
|
|
23
27
|
import { parseMemberRole, roleAllows } from '../workspace/roles.ts'
|
|
24
28
|
import { SAMPLE_DATA_FIXTURE } from './fixture.ts'
|
|
25
|
-
import type { Fixture } from './fixture.ts'
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Installs the sample workspace, in one transaction.
|
|
@@ -30,9 +33,13 @@ import type { Fixture } from './fixture.ts'
|
|
|
30
33
|
* The whole seed is one write. A failure part-way through rolls the whole
|
|
31
34
|
* thing back, so a workspace never ends up with half of it.
|
|
32
35
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
+
* Existing companies and people stay. The fixture is inserted next to them.
|
|
37
|
+
* A second install of the same fixture still fails: people emails and company
|
|
38
|
+
* domains are unique per workspace, and that unique violation becomes a 409.
|
|
39
|
+
*
|
|
40
|
+
* A workspace that has switched a toggleable module off (deals, opportunities,
|
|
41
|
+
* raises, partnerships, events) does not get fixture rows for that module.
|
|
42
|
+
* Companies, people, hiring, and enquiries still install.
|
|
36
43
|
*/
|
|
37
44
|
|
|
38
45
|
export interface SampleDataDependencies {
|
|
@@ -40,6 +47,7 @@ export interface SampleDataDependencies {
|
|
|
40
47
|
readonly transaction: TransactionScope
|
|
41
48
|
readonly createId: IdFactory
|
|
42
49
|
readonly now: () => Date
|
|
50
|
+
readonly entitlements: EntitlementRegistry
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
export interface SampleDataCounts {
|
|
@@ -55,6 +63,8 @@ export interface SampleDataCounts {
|
|
|
55
63
|
readonly enquiries: number
|
|
56
64
|
readonly roles: number
|
|
57
65
|
readonly candidates: number
|
|
66
|
+
readonly events: number
|
|
67
|
+
readonly attendances: number
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
export interface SampleDataService {
|
|
@@ -97,6 +107,15 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
97
107
|
}
|
|
98
108
|
}
|
|
99
109
|
|
|
110
|
+
async function moduleIsOn(workspaceId: string, moduleId: string): Promise<boolean> {
|
|
111
|
+
const entitlement = await dependencies.entitlements.check(
|
|
112
|
+
workspaceId,
|
|
113
|
+
moduleCapabilityName(moduleId),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return entitlement.kind === 'flag' ? entitlement.granted : true
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
return {
|
|
101
120
|
async install(actor, workspaceId) {
|
|
102
121
|
// The path parameter must match the actor's workspace, and it must be an
|
|
@@ -106,26 +125,15 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
106
125
|
await requireAdmin(actor, workspaceId)
|
|
107
126
|
|
|
108
127
|
const fixture = SAMPLE_DATA_FIXTURE
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.select({ value: count() })
|
|
119
|
-
.from(people)
|
|
120
|
-
.where(eq(people.workspaceId, workspaceId))
|
|
121
|
-
|
|
122
|
-
const alreadyHasData =
|
|
123
|
-
(companiesTally?.value ?? 0) > 0 || (peopleTally?.value ?? 0) > 0
|
|
124
|
-
|
|
125
|
-
if (alreadyHasData) {
|
|
126
|
-
throw AppError.conflict('This workspace already has CRM data')
|
|
127
|
-
}
|
|
128
|
-
|
|
128
|
+
const includeDeals = await moduleIsOn(workspaceId, 'deals')
|
|
129
|
+
const includeOpportunities = await moduleIsOn(workspaceId, 'opportunities')
|
|
130
|
+
const includeRaises = await moduleIsOn(workspaceId, 'raises')
|
|
131
|
+
const includePartnerships = await moduleIsOn(workspaceId, 'partnerships')
|
|
132
|
+
const includeEvents = await moduleIsOn(workspaceId, 'events')
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
return await dependencies.transaction(
|
|
136
|
+
async ({ tx }) => {
|
|
129
137
|
// Every stage this workspace carries, grouped by kind so a deal, an
|
|
130
138
|
// opportunity, a raise and a partnership all resolve their fixture
|
|
131
139
|
// stage slugs against the same read.
|
|
@@ -165,6 +173,10 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
165
173
|
const enquiryIds = new Map<string, string>()
|
|
166
174
|
const roleIds = new Map<string, string>()
|
|
167
175
|
const candidateIds = new Map<string, string>()
|
|
176
|
+
let planItemCount = 0
|
|
177
|
+
let notesWritten = 0
|
|
178
|
+
let eventCount = 0
|
|
179
|
+
let attendanceCount = 0
|
|
168
180
|
|
|
169
181
|
for (const record of fixture.companies) {
|
|
170
182
|
const id = dependencies.createId('company')
|
|
@@ -179,8 +191,7 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
179
191
|
description: record.description,
|
|
180
192
|
stage: record.stage,
|
|
181
193
|
sizeBand: record.sizeBand,
|
|
182
|
-
|
|
183
|
-
website: record.website,
|
|
194
|
+
addresses: [...record.addresses],
|
|
184
195
|
accountType: record.accountType,
|
|
185
196
|
icpFit: record.icpFit,
|
|
186
197
|
techStack: [...record.techStack],
|
|
@@ -202,7 +213,7 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
202
213
|
firstName: record.firstName,
|
|
203
214
|
lastName: record.lastName,
|
|
204
215
|
email: record.email,
|
|
205
|
-
|
|
216
|
+
addresses: [...record.addresses],
|
|
206
217
|
preferredChannel: record.preferredChannel,
|
|
207
218
|
influence: record.influence,
|
|
208
219
|
relationship: record.relationship,
|
|
@@ -234,6 +245,7 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
234
245
|
})
|
|
235
246
|
}
|
|
236
247
|
|
|
248
|
+
if (includeDeals) {
|
|
237
249
|
for (const record of fixture.deals) {
|
|
238
250
|
const stageId = stageIdByKindAndSlug.get(stageLookupKey('deal', record.stageSlug))
|
|
239
251
|
const companyId = companyIds.get(record.companyKey)
|
|
@@ -289,7 +301,9 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
289
301
|
})
|
|
290
302
|
}
|
|
291
303
|
}
|
|
304
|
+
}
|
|
292
305
|
|
|
306
|
+
if (includeOpportunities) {
|
|
293
307
|
for (const record of fixture.opportunities) {
|
|
294
308
|
const stageId = stageIdByKindAndSlug.get(
|
|
295
309
|
stageLookupKey('opportunity', record.stageSlug),
|
|
@@ -327,7 +341,9 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
327
341
|
updatedAt: now,
|
|
328
342
|
})
|
|
329
343
|
}
|
|
344
|
+
}
|
|
330
345
|
|
|
346
|
+
if (includeRaises) {
|
|
331
347
|
for (const record of fixture.raises) {
|
|
332
348
|
const stageId = stageIdByKindAndSlug.get(stageLookupKey('raise', record.stageSlug))
|
|
333
349
|
const companyId = companyIds.get(record.companyKey)
|
|
@@ -381,7 +397,9 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
381
397
|
})
|
|
382
398
|
}
|
|
383
399
|
}
|
|
400
|
+
}
|
|
384
401
|
|
|
402
|
+
if (includePartnerships) {
|
|
385
403
|
for (const record of fixture.partnerships) {
|
|
386
404
|
const stageId = stageIdByKindAndSlug.get(
|
|
387
405
|
stageLookupKey('partnership', record.stageSlug),
|
|
@@ -437,6 +455,7 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
437
455
|
})
|
|
438
456
|
}
|
|
439
457
|
}
|
|
458
|
+
}
|
|
440
459
|
|
|
441
460
|
for (const record of fixture.enquiries) {
|
|
442
461
|
const stageId = stageIdByKindAndSlug.get(
|
|
@@ -552,7 +571,81 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
552
571
|
})
|
|
553
572
|
}
|
|
554
573
|
|
|
574
|
+
if (includeEvents) {
|
|
575
|
+
for (const record of fixture.events) {
|
|
576
|
+
const startsAt = new Date(now.getTime() + record.startOffsetHours * 60 * 60 * 1000)
|
|
577
|
+
const endsAt = new Date(startsAt.getTime() + record.durationHours * 60 * 60 * 1000)
|
|
578
|
+
const id = dependencies.createId('crmEvent')
|
|
579
|
+
eventCount += 1
|
|
580
|
+
|
|
581
|
+
await tx.insert(events).values({
|
|
582
|
+
id,
|
|
583
|
+
workspaceId,
|
|
584
|
+
name: record.name,
|
|
585
|
+
kind: record.kind,
|
|
586
|
+
startsAt,
|
|
587
|
+
endsAt,
|
|
588
|
+
timezone: record.timezone,
|
|
589
|
+
location: record.location,
|
|
590
|
+
meetingUrl: record.meetingUrl,
|
|
591
|
+
format: record.format,
|
|
592
|
+
details: record.details,
|
|
593
|
+
status: record.status,
|
|
594
|
+
summary: record.summary,
|
|
595
|
+
tags: [...record.tags],
|
|
596
|
+
createdAt: now,
|
|
597
|
+
updatedAt: now,
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
if (record.dealKey !== null && includeDeals) {
|
|
601
|
+
const dealId = dealIds.get(record.dealKey)
|
|
602
|
+
|
|
603
|
+
if (dealId === undefined) {
|
|
604
|
+
throw new Error(
|
|
605
|
+
`Sample event "${record.name}" names unknown deal "${record.dealKey}"`,
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
await tx.insert(eventAssociations).values({
|
|
610
|
+
id: dependencies.createId('eventAssociation'),
|
|
611
|
+
workspaceId,
|
|
612
|
+
eventId: id,
|
|
613
|
+
targetType: 'deal',
|
|
614
|
+
targetId: dealId,
|
|
615
|
+
createdAt: now,
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
for (const attendee of record.attendees) {
|
|
620
|
+
const personId = personIds.get(attendee.personKey)
|
|
621
|
+
|
|
622
|
+
if (personId === undefined) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Sample event "${record.name}" names unknown person "${attendee.personKey}"`,
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
attendanceCount += 1
|
|
629
|
+
|
|
630
|
+
await tx.insert(attendances).values({
|
|
631
|
+
id: dependencies.createId('attendance'),
|
|
632
|
+
workspaceId,
|
|
633
|
+
eventId: id,
|
|
634
|
+
personId,
|
|
635
|
+
status: attendee.status,
|
|
636
|
+
source: attendee.source,
|
|
637
|
+
createdAt: now,
|
|
638
|
+
updatedAt: now,
|
|
639
|
+
})
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
555
644
|
for (const record of fixture.plans) {
|
|
645
|
+
if (!includeDeals) {
|
|
646
|
+
continue
|
|
647
|
+
}
|
|
648
|
+
|
|
556
649
|
const dealId = dealIds.get(record.targetDealKey)
|
|
557
650
|
|
|
558
651
|
if (dealId === undefined) {
|
|
@@ -561,6 +654,8 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
561
654
|
)
|
|
562
655
|
}
|
|
563
656
|
|
|
657
|
+
planItemCount += 1
|
|
658
|
+
|
|
564
659
|
await tx.insert(planItems).values({
|
|
565
660
|
id: dependencies.createId('planItem'),
|
|
566
661
|
workspaceId,
|
|
@@ -587,11 +682,17 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
587
682
|
})
|
|
588
683
|
|
|
589
684
|
if (targetId === undefined) {
|
|
685
|
+
if (isOptionalModuleNoteTarget(record.targetType)) {
|
|
686
|
+
continue
|
|
687
|
+
}
|
|
688
|
+
|
|
590
689
|
throw new Error(
|
|
591
690
|
`Sample note names unknown ${record.targetType} "${record.targetKey}"`,
|
|
592
691
|
)
|
|
593
692
|
}
|
|
594
693
|
|
|
694
|
+
notesWritten += 1
|
|
695
|
+
|
|
595
696
|
await tx.insert(notes).values({
|
|
596
697
|
id: dependencies.createId('note'),
|
|
597
698
|
workspaceId,
|
|
@@ -604,10 +705,34 @@ export function createSampleDataService(dependencies: SampleDataDependencies): S
|
|
|
604
705
|
})
|
|
605
706
|
}
|
|
606
707
|
|
|
607
|
-
return
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
708
|
+
return {
|
|
709
|
+
companies: fixture.companies.length,
|
|
710
|
+
people: fixture.people.length,
|
|
711
|
+
positions: fixture.positions.length,
|
|
712
|
+
deals: dealIds.size,
|
|
713
|
+
planItems: planItemCount,
|
|
714
|
+
notes: notesWritten,
|
|
715
|
+
opportunities: opportunityIds.size,
|
|
716
|
+
raises: raiseIds.size,
|
|
717
|
+
partnerships: partnershipIds.size,
|
|
718
|
+
enquiries: enquiryIds.size,
|
|
719
|
+
roles: roleIds.size,
|
|
720
|
+
candidates: candidateIds.size,
|
|
721
|
+
events: eventCount,
|
|
722
|
+
attendances: attendanceCount,
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
{ workspaceId, actor: toEventActor(actor) },
|
|
726
|
+
)
|
|
727
|
+
} catch (error: unknown) {
|
|
728
|
+
if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
|
|
729
|
+
throw AppError.conflict(
|
|
730
|
+
'A company domain or person email in the sample set already exists in this workspace',
|
|
731
|
+
)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
throw error
|
|
735
|
+
}
|
|
611
736
|
},
|
|
612
737
|
}
|
|
613
738
|
}
|
|
@@ -661,19 +786,11 @@ function stageLookupKey(kind: string, slug: string): string {
|
|
|
661
786
|
return `${kind}::${slug}`
|
|
662
787
|
}
|
|
663
788
|
|
|
664
|
-
function
|
|
665
|
-
return
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
notes: fixture.notes.length,
|
|
672
|
-
opportunities: fixture.opportunities.length,
|
|
673
|
-
raises: fixture.raises.length,
|
|
674
|
-
partnerships: fixture.partnerships.length,
|
|
675
|
-
enquiries: fixture.enquiries.length,
|
|
676
|
-
roles: fixture.roles.length,
|
|
677
|
-
candidates: fixture.candidates.length,
|
|
678
|
-
}
|
|
789
|
+
function isOptionalModuleNoteTarget(targetType: string): boolean {
|
|
790
|
+
return (
|
|
791
|
+
targetType === 'deal' ||
|
|
792
|
+
targetType === 'opportunity' ||
|
|
793
|
+
targetType === 'raise' ||
|
|
794
|
+
targetType === 'partnership'
|
|
795
|
+
)
|
|
679
796
|
}
|
|
@@ -8,8 +8,9 @@ import type { SampleDataService } from './service.ts'
|
|
|
8
8
|
/**
|
|
9
9
|
* `sample_data_install`: the MCP mirror of `POST /v1/workspaces/:id/sample-data`.
|
|
10
10
|
*
|
|
11
|
-
* Same admin check
|
|
12
|
-
*
|
|
11
|
+
* Same admin check. Adds the fixture next to existing records. Fails when a
|
|
12
|
+
* sample email or domain is already in the workspace. The tool takes no
|
|
13
|
+
* arguments: it installs into the actor's own workspace.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
const installArgs = z.strictObject({})
|
|
@@ -21,7 +22,7 @@ export function registerSampleDataTools(
|
|
|
21
22
|
mcp.tool({
|
|
22
23
|
name: 'sample_data_install',
|
|
23
24
|
description:
|
|
24
|
-
'Populate this workspace with a small sample of companies, people,
|
|
25
|
+
'Populate this workspace with a small sample of companies, people, and other CRM records. Adds them next to existing records. Skips objects for modules that are switched off. Fails if a sample email or domain already exists.',
|
|
25
26
|
inputSchema: installArgs,
|
|
26
27
|
async invoke(_args, actor) {
|
|
27
28
|
const counts = await service.install(actor, requireWorkspaceId(actor))
|
|
@@ -4,7 +4,7 @@ import { createSearchService } from './service.ts'
|
|
|
4
4
|
import { registerSearchTools } from './tools.ts'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* One box across
|
|
7
|
+
* One box across twelve collections.
|
|
8
8
|
*
|
|
9
9
|
* It contributes no tables and calls no `context.schema`. The `search_vector`
|
|
10
10
|
* column each searchable table carries belongs to that table's own module, which
|
|
@@ -34,6 +34,7 @@ export function createSearchModule(): KelpieModule {
|
|
|
34
34
|
'opportunities',
|
|
35
35
|
'partnerships',
|
|
36
36
|
'raises',
|
|
37
|
+
'events',
|
|
37
38
|
'hiring',
|
|
38
39
|
'plans',
|
|
39
40
|
'decisions',
|
|
@@ -18,6 +18,7 @@ import { pipelineStages } from '../pipelines/schema.ts'
|
|
|
18
18
|
import { planItems } from '../plans/schema.ts'
|
|
19
19
|
import { positions } from '../positions/schema.ts'
|
|
20
20
|
import { raises } from '../raises/schema.ts'
|
|
21
|
+
import { events } from '../events/schema.ts'
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* The reads behind `GET /v1/search`: one query per collection, each against that
|
|
@@ -211,7 +212,7 @@ function byPlanItem(
|
|
|
211
212
|
db: Queryable,
|
|
212
213
|
workspaceId: string,
|
|
213
214
|
query: SQL,
|
|
214
|
-
targetType: 'deal' | 'opportunity' | 'raise' | 'enquiry',
|
|
215
|
+
targetType: 'deal' | 'opportunity' | 'raise' | 'enquiry' | 'event',
|
|
215
216
|
) {
|
|
216
217
|
return db
|
|
217
218
|
.select({ id: planItems.targetId, rank: relatedRank(planItems.searchVector, query).as('rank') })
|
|
@@ -370,6 +371,36 @@ export async function searchPartnerships(
|
|
|
370
371
|
return collect('partnership', rows)
|
|
371
372
|
}
|
|
372
373
|
|
|
374
|
+
export async function searchEvents(
|
|
375
|
+
db: Queryable,
|
|
376
|
+
workspaceId: string,
|
|
377
|
+
query: SQL,
|
|
378
|
+
limit: number,
|
|
379
|
+
): Promise<CollectionHits> {
|
|
380
|
+
const own = db
|
|
381
|
+
.select({ id: events.id, rank: rank(events.searchVector, query).as('rank') })
|
|
382
|
+
.from(events)
|
|
383
|
+
.where(and(eq(events.workspaceId, workspaceId), matches(events.searchVector, query)))
|
|
384
|
+
|
|
385
|
+
const hits = unionAll(own, byPlanItem(db, workspaceId, query, 'event')).as('hits')
|
|
386
|
+
|
|
387
|
+
const rows = await db
|
|
388
|
+
.select({
|
|
389
|
+
id: events.id,
|
|
390
|
+
title: events.name,
|
|
391
|
+
subtitle: events.kind,
|
|
392
|
+
snippetSource: events.summary,
|
|
393
|
+
total,
|
|
394
|
+
})
|
|
395
|
+
.from(hits)
|
|
396
|
+
.innerJoin(events, eq(events.id, hits.id))
|
|
397
|
+
.groupBy(events.id)
|
|
398
|
+
.orderBy(desc(bestRank(hits.rank)), asc(events.id))
|
|
399
|
+
.limit(limit)
|
|
400
|
+
|
|
401
|
+
return collect('event', rows)
|
|
402
|
+
}
|
|
403
|
+
|
|
373
404
|
export async function searchDecisions(
|
|
374
405
|
db: Queryable,
|
|
375
406
|
workspaceId: string,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
searchDeals,
|
|
12
12
|
searchDecisions,
|
|
13
13
|
searchEnquiries,
|
|
14
|
+
searchEvents,
|
|
14
15
|
searchHandbookPages,
|
|
15
16
|
searchOpportunities,
|
|
16
17
|
searchPartnerships,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
import type { CollectionHits } from './repository.ts'
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* One box across
|
|
25
|
+
* One box across twelve collections, ranked per collection rather than merged.
|
|
25
26
|
*
|
|
26
27
|
* A single merged ranking would need `ts_rank` values from different tables to
|
|
27
28
|
* mean the same thing, and they do not: a handbook page is a thousand words and a
|
|
@@ -50,7 +51,7 @@ export interface SearchResults {
|
|
|
50
51
|
readonly limit: number
|
|
51
52
|
/** Across every group. The number a "12 results" heading is built from. */
|
|
52
53
|
readonly total: number
|
|
53
|
-
/** Always
|
|
54
|
+
/** Always every collection, in `SEARCH_COLLECTIONS` order, empty ones included. */
|
|
54
55
|
readonly groups: readonly SearchGroup[]
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -95,6 +96,7 @@ const SEARCHES: Readonly<Record<SearchCollection, CollectionSearch>> = {
|
|
|
95
96
|
opportunity: searchOpportunities,
|
|
96
97
|
raise: searchRaises,
|
|
97
98
|
partnership: searchPartnerships,
|
|
99
|
+
event: searchEvents,
|
|
98
100
|
decision: searchDecisions,
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -136,8 +138,8 @@ export function createSearchService(dependencies: SearchServiceDependencies): Se
|
|
|
136
138
|
const query = compileQuery(tsQuery)
|
|
137
139
|
const words = searchWords(request.term)
|
|
138
140
|
|
|
139
|
-
//
|
|
140
|
-
// in sequence they would be
|
|
141
|
+
// Independent reads with nothing to share but the compiled query. Run
|
|
142
|
+
// in sequence they would be round trips deep rather than wide.
|
|
141
143
|
const groups = await Promise.all(
|
|
142
144
|
wanted.map(async (collection) =>
|
|
143
145
|
toGroup(await SEARCHES[collection](dependencies.db, workspaceId, query, limit), words),
|
|
@@ -15,6 +15,12 @@ import type { KelpieModule } from '../../runtime/module.ts'
|
|
|
15
15
|
* or another provider module's name). Reads `EMAIL_FROM` and `SMTP_*` from the
|
|
16
16
|
* environment; missing or malformed values fail boot.
|
|
17
17
|
*
|
|
18
|
+
* `SMTP_USER` and `SMTP_PASSWORD` are optional and go together. Set both to
|
|
19
|
+
* authenticate against the relay. Omit both to connect without SMTP AUTH,
|
|
20
|
+
* which is how local catch-alls (maildev, MailHog, smtp4dev) work. Setting
|
|
21
|
+
* exactly one fails boot: a half-configured credential pair is a bug, not a
|
|
22
|
+
* mode.
|
|
23
|
+
*
|
|
18
24
|
* A commercial email integration (Resend, Postmark) belongs in its own module
|
|
19
25
|
* following the same shape, registered under a different name.
|
|
20
26
|
*/
|
|
@@ -27,14 +33,26 @@ export const SMTP_EMAIL_PROVIDER = 'smtp'
|
|
|
27
33
|
|
|
28
34
|
export type { EmailMessage } from '../../lib/email.ts'
|
|
29
35
|
|
|
30
|
-
const smtpEmailConfigSchema = z
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
export const smtpEmailConfigSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
EMAIL_FROM: z.string().min(1),
|
|
39
|
+
SMTP_HOST: z.string().min(1),
|
|
40
|
+
SMTP_PORT: z.coerce.number().int().positive().max(65535),
|
|
41
|
+
SMTP_SECURE: z.enum(['true', 'false']).transform((value) => value === 'true'),
|
|
42
|
+
SMTP_USER: z.string().min(1).optional(),
|
|
43
|
+
SMTP_PASSWORD: z.string().min(1).optional(),
|
|
44
|
+
})
|
|
45
|
+
.superRefine((config, ctx) => {
|
|
46
|
+
// A half-set pair is a misconfiguration, not a mode. Report both keys so
|
|
47
|
+
// the operator sees which side of the pair is missing without having to
|
|
48
|
+
// guess.
|
|
49
|
+
if ((config.SMTP_USER === undefined) !== (config.SMTP_PASSWORD === undefined)) {
|
|
50
|
+
const message = 'SMTP_USER and SMTP_PASSWORD must be set together, or both omitted for an unauthenticated relay'
|
|
51
|
+
|
|
52
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message, path: ['SMTP_USER'] })
|
|
53
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message, path: ['SMTP_PASSWORD'] })
|
|
54
|
+
}
|
|
55
|
+
})
|
|
38
56
|
|
|
39
57
|
export type SmtpEmailConfig = z.infer<typeof smtpEmailConfigSchema>
|
|
40
58
|
|
|
@@ -54,11 +72,13 @@ export interface SmtpTransport {
|
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
function createNodemailerTransport(config: SmtpEmailConfig): SmtpTransport {
|
|
75
|
+
const authenticated = config.SMTP_USER !== undefined && config.SMTP_PASSWORD !== undefined
|
|
76
|
+
|
|
57
77
|
return nodemailer.createTransport({
|
|
58
78
|
host: config.SMTP_HOST,
|
|
59
79
|
port: config.SMTP_PORT,
|
|
60
80
|
secure: config.SMTP_SECURE,
|
|
61
|
-
auth: { user: config.SMTP_USER, pass: config.SMTP_PASSWORD },
|
|
81
|
+
...(authenticated ? { auth: { user: config.SMTP_USER, pass: config.SMTP_PASSWORD } } : {}),
|
|
62
82
|
})
|
|
63
83
|
}
|
|
64
84
|
|
|
@@ -100,7 +120,9 @@ export interface SmtpEmailModuleOptions {
|
|
|
100
120
|
* The built-in SMTP module. Registers a factory under `SMTP_EMAIL_PROVIDER`;
|
|
101
121
|
* the factory reads the SMTP environment and builds the nodemailer sender, but
|
|
102
122
|
* only if the assembly's `email.provider` picks this module. Boot fails loudly
|
|
103
|
-
*
|
|
123
|
+
* on a missing or malformed required variable, or on a half-set
|
|
124
|
+
* `SMTP_USER` / `SMTP_PASSWORD` pair. Omitting both credentials is valid and
|
|
125
|
+
* makes the module connect without SMTP AUTH.
|
|
104
126
|
*
|
|
105
127
|
* The module stays in `coreModules` unconditionally: on any other provider
|
|
106
128
|
* (e.g. `EMAIL_PROVIDER=log`) the factory never runs, so the SMTP environment
|
|
@@ -6,6 +6,7 @@ import { users } from '../auth/schema.ts'
|
|
|
6
6
|
import { consentPurposes } from '../consent-purposes/schema.ts'
|
|
7
7
|
import { deals } from '../deals/schema.ts'
|
|
8
8
|
import { decisions } from '../decisions/schema.ts'
|
|
9
|
+
import { events } from '../events/schema.ts'
|
|
9
10
|
import { handbookPages } from '../handbook/schema.ts'
|
|
10
11
|
import { notes } from '../notes/schema.ts'
|
|
11
12
|
import { opportunities } from '../opportunities/schema.ts'
|
|
@@ -138,6 +139,7 @@ export async function countMemberReferences(
|
|
|
138
139
|
): Promise<readonly { readonly type: string; readonly count: number }[]> {
|
|
139
140
|
const sources = [
|
|
140
141
|
{ type: 'deal', table: deals, column: deals.ownerId, workspaceId: deals.workspaceId },
|
|
142
|
+
{ type: 'event', table: events, column: events.ownerId, workspaceId: events.workspaceId },
|
|
141
143
|
{ type: 'opportunity', table: opportunities, column: opportunities.ownerId, workspaceId: opportunities.workspaceId },
|
|
142
144
|
{ type: 'partnership', table: partnerships, column: partnerships.ownerId, workspaceId: partnerships.workspaceId },
|
|
143
145
|
{ type: 'raise', table: raises, column: raises.ownerId, workspaceId: raises.workspaceId },
|
|
@@ -366,6 +368,22 @@ export async function deleteInvite(db: Queryable, id: string): Promise<void> {
|
|
|
366
368
|
await db.delete(invites).where(eq(invites.id, id))
|
|
367
369
|
}
|
|
368
370
|
|
|
371
|
+
export async function deleteAllHandbookPages(db: Queryable, workspaceId: string): Promise<number> {
|
|
372
|
+
const deleted = await db
|
|
373
|
+
.delete(handbookPages)
|
|
374
|
+
.where(eq(handbookPages.workspaceId, workspaceId))
|
|
375
|
+
.returning({ id: handbookPages.id })
|
|
376
|
+
|
|
377
|
+
return deleted.length
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export async function listHandbookPages(
|
|
381
|
+
db: Queryable,
|
|
382
|
+
workspaceId: string,
|
|
383
|
+
): Promise<(typeof handbookPages.$inferSelect)[]> {
|
|
384
|
+
return db.select().from(handbookPages).where(eq(handbookPages.workspaceId, workspaceId))
|
|
385
|
+
}
|
|
386
|
+
|
|
369
387
|
export async function insertHandbookPages(
|
|
370
388
|
db: Queryable,
|
|
371
389
|
values: readonly (typeof handbookPages.$inferInsert)[],
|
|
@@ -9,6 +9,7 @@ import { resolveActorFrom } from '../auth/credentials.ts'
|
|
|
9
9
|
import type { CredentialDependencies } from '../auth/credentials.ts'
|
|
10
10
|
import { INVITABLE_ROLES, MEMBER_ROLES } from './roles.ts'
|
|
11
11
|
import type { InviteView, MemberView, ModuleSettingView, WorkspaceService, WorkspaceView } from './service.ts'
|
|
12
|
+
import { HANDBOOK_TEMPLATE_IDS } from './starters.ts'
|
|
12
13
|
|
|
13
14
|
/** Wire shapes for `/v1/workspaces`, per `onboarding.md`'s API sketch. */
|
|
14
15
|
|
|
@@ -21,6 +22,11 @@ const createBody = z.object({
|
|
|
21
22
|
timezone: timezoneSchema,
|
|
22
23
|
})
|
|
23
24
|
|
|
25
|
+
const seedHandbookBodySchema = z.object({
|
|
26
|
+
handbook_template: z.enum(HANDBOOK_TEMPLATE_IDS),
|
|
27
|
+
replace: z.boolean().optional().default(false),
|
|
28
|
+
})
|
|
29
|
+
|
|
24
30
|
export const updateBody = z
|
|
25
31
|
.object({
|
|
26
32
|
name: z.string().min(1),
|
|
@@ -106,6 +112,16 @@ export function mountWorkspaceRoutes(router: Hono, dependencies: WorkspaceRoutes
|
|
|
106
112
|
return context.json(workspaceResponse(workspace), 201)
|
|
107
113
|
})
|
|
108
114
|
|
|
115
|
+
router.post('/workspaces/:id/handbook/seed', async (context) => {
|
|
116
|
+
const body = await readBody(context, seedHandbookBodySchema)
|
|
117
|
+
const result = await dependencies.service.seedHandbook(await requireActor(context), context.req.param('id'), {
|
|
118
|
+
handbookTemplate: body.handbook_template,
|
|
119
|
+
replace: body.replace,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return context.json({ handbook_pages: result.handbookPages }, 201)
|
|
123
|
+
})
|
|
124
|
+
|
|
109
125
|
router.get('/workspaces/:id', async (context) => {
|
|
110
126
|
const workspace = await dependencies.service.get(await requireActor(context), context.req.param('id'))
|
|
111
127
|
|