@kelpie/server 0.12.0 → 0.14.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 +9 -0
- package/dist/boot.d.ts.map +1 -1
- package/dist/boot.js +12 -2
- package/dist/boot.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/apiKeyScopes.d.ts.map +1 -1
- package/dist/lib/apiKeyScopes.js +6 -0
- package/dist/lib/apiKeyScopes.js.map +1 -1
- package/dist/lib/columns.d.ts +6 -0
- package/dist/lib/columns.d.ts.map +1 -1
- package/dist/lib/columns.js +5 -3
- package/dist/lib/columns.js.map +1 -1
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/egress.d.ts.map +1 -1
- package/dist/lib/egress.js +8 -4
- package/dist/lib/egress.js.map +1 -1
- package/dist/lib/errors.d.ts +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/jobs.d.ts +110 -0
- package/dist/lib/jobs.d.ts.map +1 -0
- package/dist/lib/jobs.js +5 -0
- package/dist/lib/jobs.js.map +1 -0
- 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/jobs.d.ts +63 -0
- package/dist/runtime/jobs.d.ts.map +1 -0
- package/dist/runtime/jobs.js +231 -0
- package/dist/runtime/jobs.js.map +1 -0
- package/dist/runtime/module.d.ts +20 -0
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts +8 -0
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +117 -7
- package/dist/runtime/registry.js.map +1 -1
- package/dist/runtime/transaction.d.ts +19 -0
- package/dist/runtime/transaction.d.ts.map +1 -1
- package/dist/runtime/transaction.js +7 -0
- package/dist/runtime/transaction.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 +15 -0
- package/dist/testing/app.d.ts.map +1 -1
- package/dist/testing/app.js +7 -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/dist/testing/database.d.ts +7 -0
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +26 -2
- package/dist/testing/database.js.map +1 -1
- package/dist/testing/services.d.ts +7 -0
- package/dist/testing/services.d.ts.map +1 -1
- package/dist/testing/services.js +8 -1
- package/dist/testing/services.js.map +1 -1
- package/dist/worker.d.ts +15 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +19 -0
- package/dist/worker.js.map +1 -0
- package/migrations/0044_events.sql +79 -0
- package/migrations/0045_addresses.sql +66 -0
- package/migrations/0046_drop_company_website.sql +66 -0
- package/migrations/meta/_journal.json +21 -0
- package/package.json +4 -3
- package/src/app.ts +25 -1
- package/src/boot.ts +21 -2
- package/src/index.ts +17 -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/jobs.ts +126 -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/jobs.ts +358 -0
- package/src/runtime/module.ts +20 -0
- package/src/runtime/registry.ts +146 -8
- package/src/runtime/transaction.ts +43 -0
- package/src/schema/index.ts +1 -0
- package/src/testing/app.ts +22 -1
- package/src/testing/client.ts +11 -1
- package/src/testing/database.ts +35 -2
- package/src/testing/services.ts +15 -1
- package/src/worker.ts +20 -0
|
@@ -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
|
/**
|
|
@@ -9,6 +9,7 @@ import { activities } from '../activities/schema.ts'
|
|
|
9
9
|
import { decisions } from '../decisions/schema.ts'
|
|
10
10
|
import { deals } from '../deals/schema.ts'
|
|
11
11
|
import { enquiries } from '../enquiries/schema.ts'
|
|
12
|
+
import { attendances, events } from '../events/schema.ts'
|
|
12
13
|
import { notes } from '../notes/schema.ts'
|
|
13
14
|
import { opportunities } from '../opportunities/schema.ts'
|
|
14
15
|
import { partnerships } from '../partnerships/schema.ts'
|
|
@@ -242,6 +243,69 @@ export async function listPartnershipTouchpoints(
|
|
|
242
243
|
)
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
/** A scheduled Event whose start is inside `[from, to)`. */
|
|
247
|
+
export interface UpcomingEventRecord {
|
|
248
|
+
readonly id: string
|
|
249
|
+
readonly name: string
|
|
250
|
+
readonly startsAt: Date
|
|
251
|
+
readonly endsAt: Date
|
|
252
|
+
readonly format: string
|
|
253
|
+
readonly location: string
|
|
254
|
+
readonly attendeeCount: number
|
|
255
|
+
readonly ownerId: string | null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function upcomingEventConditions(workspaceId: string, from: Date, to: Date): (SQL | undefined)[] {
|
|
259
|
+
return [
|
|
260
|
+
eq(events.workspaceId, workspaceId),
|
|
261
|
+
eq(events.status, 'scheduled'),
|
|
262
|
+
gte(events.startsAt, from),
|
|
263
|
+
lt(events.startsAt, to),
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function countUpcomingEvents(
|
|
268
|
+
db: Queryable,
|
|
269
|
+
workspaceId: string,
|
|
270
|
+
from: Date,
|
|
271
|
+
to: Date,
|
|
272
|
+
): Promise<number> {
|
|
273
|
+
const [row] = await db
|
|
274
|
+
.select({ total: count() })
|
|
275
|
+
.from(events)
|
|
276
|
+
.where(and(...upcomingEventConditions(workspaceId, from, to)))
|
|
277
|
+
|
|
278
|
+
return row?.total ?? 0
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function listUpcomingEvents(
|
|
282
|
+
db: Queryable,
|
|
283
|
+
workspaceId: string,
|
|
284
|
+
from: Date,
|
|
285
|
+
to: Date,
|
|
286
|
+
limit: number,
|
|
287
|
+
): Promise<readonly UpcomingEventRecord[]> {
|
|
288
|
+
const rows = await db
|
|
289
|
+
.select({
|
|
290
|
+
id: events.id,
|
|
291
|
+
name: events.name,
|
|
292
|
+
startsAt: events.startsAt,
|
|
293
|
+
endsAt: events.endsAt,
|
|
294
|
+
format: events.format,
|
|
295
|
+
location: events.location,
|
|
296
|
+
ownerId: events.ownerId,
|
|
297
|
+
attendeeCount: sql<number>`coalesce(count(${attendances.id}) filter (where ${attendances.status} <> 'cancelled'), 0)::int`,
|
|
298
|
+
})
|
|
299
|
+
.from(events)
|
|
300
|
+
.leftJoin(attendances, eq(attendances.eventId, events.id))
|
|
301
|
+
.where(and(...upcomingEventConditions(workspaceId, from, to)))
|
|
302
|
+
.groupBy(events.id)
|
|
303
|
+
.orderBy(asc(events.startsAt), asc(events.id))
|
|
304
|
+
.limit(limit)
|
|
305
|
+
|
|
306
|
+
return rows
|
|
307
|
+
}
|
|
308
|
+
|
|
245
309
|
/**
|
|
246
310
|
* The calendar day a contact happened on, in the workspace's zone.
|
|
247
311
|
*
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
SignalList,
|
|
16
16
|
StaleContactSignal,
|
|
17
17
|
TouchpointSignal,
|
|
18
|
+
UpcomingEventSignal,
|
|
18
19
|
} from './service.ts'
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -83,6 +84,19 @@ function touchpointBody(touchpoint: TouchpointSignal): Record<string, unknown> {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
function upcomingEventBody(event: UpcomingEventSignal): Record<string, unknown> {
|
|
88
|
+
return {
|
|
89
|
+
id: event.id,
|
|
90
|
+
name: event.name,
|
|
91
|
+
starts_at: event.startsAt.toISOString(),
|
|
92
|
+
ends_at: event.endsAt.toISOString(),
|
|
93
|
+
format: event.format,
|
|
94
|
+
location: event.location,
|
|
95
|
+
attendee_count: event.attendeeCount,
|
|
96
|
+
owner_id: event.ownerId,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
function staleContactBody(contact: StaleContactSignal): Record<string, unknown> {
|
|
87
101
|
return {
|
|
88
102
|
id: contact.id,
|
|
@@ -147,6 +161,7 @@ export function dashboardResponse(snapshot: DashboardSnapshot): Record<string, u
|
|
|
147
161
|
overdue_plan_items: signalListBody(snapshot.overduePlanItems, planItemBody),
|
|
148
162
|
due_soon_plan_items: signalListBody(snapshot.dueSoonPlanItems, planItemBody),
|
|
149
163
|
partnership_touchpoints: signalListBody(snapshot.partnershipTouchpoints, touchpointBody),
|
|
164
|
+
upcoming_events: signalListBody(snapshot.upcomingEvents, upcomingEventBody),
|
|
150
165
|
stale_contacts: signalListBody(snapshot.staleContacts, staleContactBody),
|
|
151
166
|
recent_activity: snapshot.recentActivity.map(activityBody),
|
|
152
167
|
recent_notes: snapshot.recentNotes.map(noteBody),
|
|
@@ -117,6 +117,17 @@ export interface DecisionSignal extends TargetRef {
|
|
|
117
117
|
readonly dueAt: Date | null
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
export interface UpcomingEventSignal {
|
|
121
|
+
readonly id: string
|
|
122
|
+
readonly name: string
|
|
123
|
+
readonly startsAt: Date
|
|
124
|
+
readonly endsAt: Date
|
|
125
|
+
readonly format: string
|
|
126
|
+
readonly location: string
|
|
127
|
+
readonly attendeeCount: number
|
|
128
|
+
readonly ownerId: string | null
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
export interface DashboardSnapshot {
|
|
121
132
|
readonly generatedAt: Date
|
|
122
133
|
/** The day everything below was computed against, `YYYY-MM-DD`. */
|
|
@@ -129,6 +140,7 @@ export interface DashboardSnapshot {
|
|
|
129
140
|
readonly overduePlanItems: SignalList<PlanItemSignal>
|
|
130
141
|
readonly dueSoonPlanItems: SignalList<PlanItemSignal>
|
|
131
142
|
readonly partnershipTouchpoints: SignalList<TouchpointSignal>
|
|
143
|
+
readonly upcomingEvents: SignalList<UpcomingEventSignal>
|
|
132
144
|
readonly staleContacts: SignalList<StaleContactSignal>
|
|
133
145
|
readonly recentActivity: readonly ActivitySignal[]
|
|
134
146
|
readonly recentNotes: readonly NoteSignal[]
|
|
@@ -155,6 +167,10 @@ interface DayBounds {
|
|
|
155
167
|
readonly through: string
|
|
156
168
|
/** The first day a contact still counts as fresh. */
|
|
157
169
|
readonly staleCutoff: string
|
|
170
|
+
/** Instant the upcoming Events window starts (now). */
|
|
171
|
+
readonly fromInstant: Date
|
|
172
|
+
/** Instant the upcoming Events window ends (now plus `UPCOMING_DAYS`). */
|
|
173
|
+
readonly throughInstant: Date
|
|
158
174
|
}
|
|
159
175
|
|
|
160
176
|
/** Everything read from the database, before any of it is named or shaped. */
|
|
@@ -163,6 +179,7 @@ interface SignalRows {
|
|
|
163
179
|
readonly overduePlanItems: SignalList<repository.PlanItemRecord>
|
|
164
180
|
readonly dueSoonPlanItems: SignalList<repository.PlanItemRecord>
|
|
165
181
|
readonly partnershipTouchpoints: SignalList<repository.TouchpointRecord>
|
|
182
|
+
readonly upcomingEvents: SignalList<repository.UpcomingEventRecord>
|
|
166
183
|
readonly staleContacts: SignalList<repository.StaleContactRecord>
|
|
167
184
|
readonly recentActivity: readonly repository.ActivityRecord[]
|
|
168
185
|
readonly recentNotes: readonly repository.NoteRecord[]
|
|
@@ -201,6 +218,19 @@ async function readTouchpoints(
|
|
|
201
218
|
}
|
|
202
219
|
}
|
|
203
220
|
|
|
221
|
+
async function readUpcomingEvents(
|
|
222
|
+
db: Database,
|
|
223
|
+
workspaceId: string,
|
|
224
|
+
from: Date,
|
|
225
|
+
to: Date,
|
|
226
|
+
limit: number,
|
|
227
|
+
): Promise<SignalList<repository.UpcomingEventRecord>> {
|
|
228
|
+
return {
|
|
229
|
+
total: await repository.countUpcomingEvents(db, workspaceId, from, to),
|
|
230
|
+
items: await repository.listUpcomingEvents(db, workspaceId, from, to, limit),
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
204
234
|
async function readStaleContacts(
|
|
205
235
|
db: Database,
|
|
206
236
|
workspaceId: string,
|
|
@@ -236,6 +266,7 @@ async function readSignals(
|
|
|
236
266
|
overduePlanItems,
|
|
237
267
|
dueSoonPlanItems,
|
|
238
268
|
partnershipTouchpoints,
|
|
269
|
+
upcomingEvents,
|
|
239
270
|
staleContacts,
|
|
240
271
|
recentActivity,
|
|
241
272
|
recentNotes,
|
|
@@ -245,6 +276,7 @@ async function readSignals(
|
|
|
245
276
|
readPlanItems(db, workspaceId, { before: days.today }, limit),
|
|
246
277
|
readPlanItems(db, workspaceId, { from: days.today, to: days.through }, limit),
|
|
247
278
|
readTouchpoints(db, workspaceId, days.through, limit),
|
|
279
|
+
readUpcomingEvents(db, workspaceId, days.fromInstant, days.throughInstant, limit),
|
|
248
280
|
readStaleContacts(db, workspaceId, timezone, days.staleCutoff, limit),
|
|
249
281
|
repository.listRecentActivity(db, workspaceId, limit),
|
|
250
282
|
repository.listRecentNotes(db, workspaceId, limit),
|
|
@@ -256,6 +288,7 @@ async function readSignals(
|
|
|
256
288
|
overduePlanItems,
|
|
257
289
|
dueSoonPlanItems,
|
|
258
290
|
partnershipTouchpoints,
|
|
291
|
+
upcomingEvents,
|
|
259
292
|
staleContacts,
|
|
260
293
|
recentActivity,
|
|
261
294
|
recentNotes,
|
|
@@ -376,6 +409,19 @@ function toSnapshot(
|
|
|
376
409
|
overduePlanItems: toPlanItemSignal(rows.overduePlanItems, nameOf),
|
|
377
410
|
dueSoonPlanItems: toPlanItemSignal(rows.dueSoonPlanItems, nameOf),
|
|
378
411
|
partnershipTouchpoints: toTouchpointSignal(rows.partnershipTouchpoints, days.today),
|
|
412
|
+
upcomingEvents: {
|
|
413
|
+
total: rows.upcomingEvents.total,
|
|
414
|
+
items: rows.upcomingEvents.items.map((row) => ({
|
|
415
|
+
id: row.id,
|
|
416
|
+
name: row.name,
|
|
417
|
+
startsAt: row.startsAt,
|
|
418
|
+
endsAt: row.endsAt,
|
|
419
|
+
format: row.format,
|
|
420
|
+
location: row.location,
|
|
421
|
+
attendeeCount: row.attendeeCount,
|
|
422
|
+
ownerId: row.ownerId,
|
|
423
|
+
})),
|
|
424
|
+
},
|
|
379
425
|
staleContacts: toStaleContactSignal(rows.staleContacts, days.today),
|
|
380
426
|
recentActivity: rows.recentActivity.map((row) => ({
|
|
381
427
|
id: row.id,
|
|
@@ -436,6 +482,8 @@ export function createDashboardService(dependencies: DashboardDependencies): Das
|
|
|
436
482
|
today,
|
|
437
483
|
through: upcomingEnd(today),
|
|
438
484
|
staleCutoff: staleContactCutoff(today),
|
|
485
|
+
fromInstant: generatedAt,
|
|
486
|
+
throughInstant: new Date(generatedAt.getTime() + UPCOMING_DAYS * 24 * 60 * 60 * 1000),
|
|
439
487
|
}
|
|
440
488
|
|
|
441
489
|
const rows = await readSignals(dependencies.db, workspaceId, timezone, days, limit)
|
|
@@ -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'
|
|
@@ -57,6 +58,7 @@ const NAMED_TARGET_TABLES = [
|
|
|
57
58
|
{ table: partnerships, id: partnerships.id, workspaceId: partnerships.workspaceId, name: partnerships.name },
|
|
58
59
|
{ table: raises, id: raises.id, workspaceId: raises.workspaceId, name: raises.name },
|
|
59
60
|
{ table: enquiries, id: enquiries.id, workspaceId: enquiries.workspaceId, name: enquiries.name },
|
|
61
|
+
{ table: events, id: events.id, workspaceId: events.workspaceId, name: events.name },
|
|
60
62
|
]
|
|
61
63
|
|
|
62
64
|
/**
|