@kelpie/server 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +13 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +8 -1
- package/dist/app.js.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/boot.js +1 -0
- package/dist/boot.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/apiKeyScopes.d.ts.map +1 -1
- package/dist/lib/apiKeyScopes.js +6 -0
- package/dist/lib/apiKeyScopes.js.map +1 -1
- package/dist/lib/columns.d.ts +6 -0
- package/dist/lib/columns.d.ts.map +1 -1
- package/dist/lib/columns.js +5 -3
- package/dist/lib/columns.js.map +1 -1
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/egress.d.ts.map +1 -1
- package/dist/lib/egress.js +8 -4
- package/dist/lib/egress.js.map +1 -1
- package/dist/lib/errors.d.ts +18 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +25 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/ids.d.ts +11 -0
- package/dist/lib/ids.d.ts.map +1 -1
- package/dist/lib/ids.js +11 -0
- package/dist/lib/ids.js.map +1 -1
- package/dist/lib/kelpieConfigFile.d.ts +15 -1
- package/dist/lib/kelpieConfigFile.d.ts.map +1 -1
- package/dist/lib/kelpieConfigFile.js +33 -1
- package/dist/lib/kelpieConfigFile.js.map +1 -1
- package/dist/modules/agent-tasks/catalog.d.ts.map +1 -1
- package/dist/modules/agent-tasks/catalog.js +32 -1
- package/dist/modules/agent-tasks/catalog.js.map +1 -1
- package/dist/modules/agent-tasks/index.d.ts.map +1 -1
- package/dist/modules/agent-tasks/index.js +1 -0
- package/dist/modules/agent-tasks/index.js.map +1 -1
- package/dist/modules/agent-tasks/resolve.d.ts.map +1 -1
- package/dist/modules/agent-tasks/resolve.js +5 -0
- package/dist/modules/agent-tasks/resolve.js.map +1 -1
- package/dist/modules/agent-tasks/routes.d.ts +2 -0
- package/dist/modules/agent-tasks/routes.d.ts.map +1 -1
- package/dist/modules/attachedRecords.d.ts +22 -8
- package/dist/modules/attachedRecords.d.ts.map +1 -1
- package/dist/modules/attachedRecords.js +42 -5
- package/dist/modules/attachedRecords.js.map +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +22 -4
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/repository.d.ts +16 -1
- package/dist/modules/auth/repository.d.ts.map +1 -1
- package/dist/modules/auth/repository.js +21 -2
- package/dist/modules/auth/repository.js.map +1 -1
- package/dist/modules/auth/routes.d.ts.map +1 -1
- package/dist/modules/auth/routes.js +32 -6
- package/dist/modules/auth/routes.js.map +1 -1
- package/dist/modules/auth/schema.d.ts +18 -1
- package/dist/modules/auth/schema.d.ts.map +1 -1
- package/dist/modules/auth/schema.js +12 -1
- package/dist/modules/auth/schema.js.map +1 -1
- package/dist/modules/auth/service.d.ts +46 -0
- package/dist/modules/auth/service.d.ts.map +1 -1
- package/dist/modules/auth/service.js +167 -10
- package/dist/modules/auth/service.js.map +1 -1
- package/dist/modules/auth/session.d.ts +12 -0
- package/dist/modules/auth/session.d.ts.map +1 -1
- package/dist/modules/auth/session.js +12 -0
- package/dist/modules/auth/session.js.map +1 -1
- package/dist/modules/companies/repository.d.ts.map +1 -1
- package/dist/modules/companies/repository.js +1 -1
- package/dist/modules/companies/repository.js.map +1 -1
- package/dist/modules/companies/routes.d.ts +2 -4
- package/dist/modules/companies/routes.d.ts.map +1 -1
- package/dist/modules/companies/routes.js +6 -11
- package/dist/modules/companies/routes.js.map +1 -1
- package/dist/modules/companies/schema.d.ts +13 -28
- package/dist/modules/companies/schema.d.ts.map +1 -1
- package/dist/modules/companies/schema.js +2 -2
- package/dist/modules/companies/schema.js.map +1 -1
- package/dist/modules/companies/service.d.ts +2 -3
- package/dist/modules/companies/service.d.ts.map +1 -1
- package/dist/modules/companies/service.js +4 -7
- package/dist/modules/companies/service.js.map +1 -1
- package/dist/modules/core.d.ts.map +1 -1
- package/dist/modules/core.js +2 -0
- package/dist/modules/core.js.map +1 -1
- package/dist/modules/custom-fields/repository.d.ts.map +1 -1
- package/dist/modules/custom-fields/repository.js +2 -0
- package/dist/modules/custom-fields/repository.js.map +1 -1
- package/dist/modules/custom-fields/routes.d.ts +1 -0
- package/dist/modules/custom-fields/routes.d.ts.map +1 -1
- package/dist/modules/dashboard/index.d.ts.map +1 -1
- package/dist/modules/dashboard/index.js +1 -0
- package/dist/modules/dashboard/index.js.map +1 -1
- package/dist/modules/dashboard/repository.d.ts +13 -0
- package/dist/modules/dashboard/repository.d.ts.map +1 -1
- package/dist/modules/dashboard/repository.js +36 -0
- package/dist/modules/dashboard/repository.js.map +1 -1
- package/dist/modules/dashboard/routes.d.ts.map +1 -1
- package/dist/modules/dashboard/routes.js +13 -0
- package/dist/modules/dashboard/routes.js.map +1 -1
- package/dist/modules/dashboard/service.d.ts +11 -0
- package/dist/modules/dashboard/service.d.ts.map +1 -1
- package/dist/modules/dashboard/service.js +24 -1
- package/dist/modules/dashboard/service.js.map +1 -1
- package/dist/modules/decisions/repository.d.ts.map +1 -1
- package/dist/modules/decisions/repository.js +2 -0
- package/dist/modules/decisions/repository.js.map +1 -1
- package/dist/modules/decisions/routes.d.ts +2 -0
- package/dist/modules/decisions/routes.d.ts.map +1 -1
- package/dist/modules/events/attendances.d.ts +35 -0
- package/dist/modules/events/attendances.d.ts.map +1 -0
- package/dist/modules/events/attendances.js +157 -0
- package/dist/modules/events/attendances.js.map +1 -0
- package/dist/modules/events/catalog.d.ts +35 -0
- package/dist/modules/events/catalog.d.ts.map +1 -0
- package/dist/modules/events/catalog.js +11 -0
- package/dist/modules/events/catalog.js.map +1 -0
- package/dist/modules/events/index.d.ts +10 -0
- package/dist/modules/events/index.d.ts.map +1 -0
- package/dist/modules/events/index.js +47 -0
- package/dist/modules/events/index.js.map +1 -0
- package/dist/modules/events/repository.d.ts +67 -0
- package/dist/modules/events/repository.d.ts.map +1 -0
- package/dist/modules/events/repository.js +212 -0
- package/dist/modules/events/repository.js.map +1 -0
- package/dist/modules/events/routes.d.ts +136 -0
- package/dist/modules/events/routes.d.ts.map +1 -0
- package/dist/modules/events/routes.js +280 -0
- package/dist/modules/events/routes.js.map +1 -0
- package/dist/modules/events/schema.d.ts +626 -0
- package/dist/modules/events/schema.d.ts.map +1 -0
- package/dist/modules/events/schema.js +93 -0
- package/dist/modules/events/schema.js.map +1 -0
- package/dist/modules/events/service.d.ts +68 -0
- package/dist/modules/events/service.d.ts.map +1 -0
- package/dist/modules/events/service.js +262 -0
- package/dist/modules/events/service.js.map +1 -0
- package/dist/modules/events/tools.d.ts +8 -0
- package/dist/modules/events/tools.d.ts.map +1 -0
- package/dist/modules/events/tools.js +80 -0
- package/dist/modules/events/tools.js.map +1 -0
- package/dist/modules/forms/applySubmissionFields.d.ts.map +1 -1
- package/dist/modules/forms/applySubmissionFields.js +7 -5
- package/dist/modules/forms/applySubmissionFields.js.map +1 -1
- package/dist/modules/forms/fillAddresses.d.ts +4 -0
- package/dist/modules/forms/fillAddresses.d.ts.map +1 -0
- package/dist/modules/forms/fillAddresses.js +93 -0
- package/dist/modules/forms/fillAddresses.js.map +1 -0
- package/dist/modules/forms/repository.d.ts +2 -2
- package/dist/modules/forms/repository.d.ts.map +1 -1
- package/dist/modules/forms/repository.js.map +1 -1
- package/dist/modules/forms/routes.d.ts +2 -0
- package/dist/modules/forms/routes.d.ts.map +1 -1
- package/dist/modules/forms/routes.js +2 -2
- package/dist/modules/forms/routes.js.map +1 -1
- package/dist/modules/forms/schema.js +2 -2
- package/dist/modules/forms/schema.js.map +1 -1
- package/dist/modules/forms/service.js +2 -2
- package/dist/modules/forms/service.js.map +1 -1
- package/dist/modules/forms/submission.d.ts +1 -0
- package/dist/modules/forms/submission.d.ts.map +1 -1
- package/dist/modules/forms/submission.js +27 -4
- package/dist/modules/forms/submission.js.map +1 -1
- package/dist/modules/hiring/roles.d.ts.map +1 -1
- package/dist/modules/hiring/roles.js +2 -1
- package/dist/modules/hiring/roles.js.map +1 -1
- package/dist/modules/import-export/addresses.d.ts +22 -0
- package/dist/modules/import-export/addresses.d.ts.map +1 -0
- package/dist/modules/import-export/addresses.js +83 -0
- package/dist/modules/import-export/addresses.js.map +1 -0
- package/dist/modules/import-export/drafts.d.ts +3 -4
- package/dist/modules/import-export/drafts.d.ts.map +1 -1
- package/dist/modules/import-export/drafts.js +4 -5
- package/dist/modules/import-export/drafts.js.map +1 -1
- package/dist/modules/import-export/exportRows.d.ts.map +1 -1
- package/dist/modules/import-export/exportRows.js +3 -3
- package/dist/modules/import-export/exportRows.js.map +1 -1
- package/dist/modules/import-export/plan.d.ts.map +1 -1
- package/dist/modules/import-export/plan.js +17 -2
- package/dist/modules/import-export/plan.js.map +1 -1
- package/dist/modules/import-export/presets.d.ts.map +1 -1
- package/dist/modules/import-export/presets.js +52 -7
- package/dist/modules/import-export/presets.js.map +1 -1
- package/dist/modules/import-export/repository.d.ts +3 -4
- package/dist/modules/import-export/repository.d.ts.map +1 -1
- package/dist/modules/import-export/repository.js +2 -3
- package/dist/modules/import-export/repository.js.map +1 -1
- package/dist/modules/import-export/writes.d.ts.map +1 -1
- package/dist/modules/import-export/writes.js +8 -4
- package/dist/modules/import-export/writes.js.map +1 -1
- package/dist/modules/lists/routes.d.ts +4 -0
- package/dist/modules/lists/routes.d.ts.map +1 -1
- package/dist/modules/notes/routes.d.ts +2 -0
- package/dist/modules/notes/routes.d.ts.map +1 -1
- package/dist/modules/people/repository.d.ts.map +1 -1
- package/dist/modules/people/repository.js +1 -1
- package/dist/modules/people/repository.js.map +1 -1
- package/dist/modules/people/routes.d.ts +2 -2
- package/dist/modules/people/routes.js +6 -6
- package/dist/modules/people/routes.js.map +1 -1
- package/dist/modules/people/schema.d.ts +17 -14
- package/dist/modules/people/schema.d.ts.map +1 -1
- package/dist/modules/people/schema.js +6 -4
- package/dist/modules/people/schema.js.map +1 -1
- package/dist/modules/people/service.d.ts +2 -2
- package/dist/modules/people/service.d.ts.map +1 -1
- package/dist/modules/people/service.js +5 -4
- package/dist/modules/people/service.js.map +1 -1
- package/dist/modules/plans/repository.d.ts +2 -2
- package/dist/modules/plans/repository.d.ts.map +1 -1
- package/dist/modules/plans/routes.d.ts +1 -0
- package/dist/modules/plans/routes.d.ts.map +1 -1
- package/dist/modules/plans/routes.js +4 -4
- package/dist/modules/plans/routes.js.map +1 -1
- package/dist/modules/plans/schema.d.ts +2 -2
- package/dist/modules/plans/schema.d.ts.map +1 -1
- package/dist/modules/plans/schema.js +3 -3
- package/dist/modules/plans/schema.js.map +1 -1
- package/dist/modules/plans/service.d.ts +2 -2
- package/dist/modules/plans/service.d.ts.map +1 -1
- package/dist/modules/plans/service.js.map +1 -1
- package/dist/modules/plans/tools.js +2 -2
- package/dist/modules/plans/tools.js.map +1 -1
- package/dist/modules/recordTargets.d.ts.map +1 -1
- package/dist/modules/recordTargets.js +11 -0
- package/dist/modules/recordTargets.js.map +1 -1
- package/dist/modules/references.d.ts.map +1 -1
- package/dist/modules/references.js +2 -0
- package/dist/modules/references.js.map +1 -1
- package/dist/modules/sample-data/fixture.d.ts +26 -4
- package/dist/modules/sample-data/fixture.d.ts.map +1 -1
- package/dist/modules/sample-data/fixture.js +157 -23
- package/dist/modules/sample-data/fixture.js.map +1 -1
- package/dist/modules/sample-data/index.d.ts +5 -2
- package/dist/modules/sample-data/index.d.ts.map +1 -1
- package/dist/modules/sample-data/index.js +7 -2
- package/dist/modules/sample-data/index.js.map +1 -1
- package/dist/modules/sample-data/routes.d.ts.map +1 -1
- package/dist/modules/sample-data/routes.js +2 -0
- package/dist/modules/sample-data/routes.js.map +1 -1
- package/dist/modules/sample-data/service.d.ts +11 -3
- package/dist/modules/sample-data/service.d.ts.map +1 -1
- package/dist/modules/sample-data/service.js +452 -363
- package/dist/modules/sample-data/service.js.map +1 -1
- package/dist/modules/sample-data/tools.d.ts.map +1 -1
- package/dist/modules/sample-data/tools.js +4 -3
- package/dist/modules/sample-data/tools.js.map +1 -1
- package/dist/modules/search/index.d.ts +1 -1
- package/dist/modules/search/index.d.ts.map +1 -1
- package/dist/modules/search/index.js +2 -1
- package/dist/modules/search/index.js.map +1 -1
- package/dist/modules/search/repository.d.ts +1 -0
- package/dist/modules/search/repository.d.ts.map +1 -1
- package/dist/modules/search/repository.js +22 -0
- package/dist/modules/search/repository.js.map +1 -1
- package/dist/modules/search/service.d.ts +2 -2
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +4 -3
- package/dist/modules/search/service.js.map +1 -1
- package/dist/modules/smtp-email/index.d.ts +12 -4
- package/dist/modules/smtp-email/index.d.ts.map +1 -1
- package/dist/modules/smtp-email/index.js +25 -5
- package/dist/modules/smtp-email/index.js.map +1 -1
- package/dist/modules/workspace/repository.d.ts +2 -0
- package/dist/modules/workspace/repository.d.ts.map +1 -1
- package/dist/modules/workspace/repository.js +12 -0
- package/dist/modules/workspace/repository.js.map +1 -1
- package/dist/modules/workspace/routes.d.ts.map +1 -1
- package/dist/modules/workspace/routes.js +13 -0
- package/dist/modules/workspace/routes.js.map +1 -1
- package/dist/modules/workspace/service.d.ts +10 -0
- package/dist/modules/workspace/service.d.ts.map +1 -1
- package/dist/modules/workspace/service.js +34 -10
- package/dist/modules/workspace/service.js.map +1 -1
- package/dist/modules/workspace/starters.d.ts +17 -2
- package/dist/modules/workspace/starters.d.ts.map +1 -1
- package/dist/modules/workspace/starters.js +141 -14
- package/dist/modules/workspace/starters.js.map +1 -1
- package/dist/modules/workspace/workspaceAccessMiddleware.d.ts.map +1 -1
- package/dist/modules/workspace/workspaceAccessMiddleware.js +3 -4
- package/dist/modules/workspace/workspaceAccessMiddleware.js.map +1 -1
- package/dist/runtime/events.d.ts +1 -1
- package/dist/runtime/events.d.ts.map +1 -1
- package/dist/runtime/events.js +2 -0
- package/dist/runtime/events.js.map +1 -1
- package/dist/runtime/module.d.ts +71 -1
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +142 -9
- package/dist/runtime/registry.js.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/app.d.ts +8 -0
- package/dist/testing/app.d.ts.map +1 -1
- package/dist/testing/app.js +6 -1
- package/dist/testing/app.js.map +1 -1
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +9 -1
- package/dist/testing/client.js.map +1 -1
- package/migrations/0043_external_sign_in.sql +2 -0
- package/migrations/0044_events.sql +79 -0
- package/migrations/0045_addresses.sql +66 -0
- package/migrations/0046_drop_company_website.sql +66 -0
- package/migrations/meta/_journal.json +28 -0
- package/package.json +3 -3
- package/src/app.ts +25 -1
- package/src/boot.ts +1 -0
- package/src/index.ts +13 -1
- package/src/lib/apiKeyScopes.ts +6 -0
- package/src/lib/columns.ts +12 -3
- package/src/lib/config.ts +21 -0
- package/src/lib/egress.ts +8 -4
- package/src/lib/errors.ts +37 -0
- package/src/lib/ids.ts +11 -0
- package/src/lib/kelpieConfigFile.ts +69 -1
- package/src/modules/agent-tasks/catalog.ts +33 -1
- package/src/modules/agent-tasks/index.ts +1 -0
- package/src/modules/agent-tasks/resolve.ts +11 -0
- package/src/modules/attachedRecords.ts +85 -13
- package/src/modules/auth/index.ts +26 -4
- package/src/modules/auth/repository.ts +36 -4
- package/src/modules/auth/routes.ts +46 -9
- package/src/modules/auth/schema.ts +12 -1
- package/src/modules/auth/service.ts +261 -10
- package/src/modules/auth/session.ts +14 -0
- package/src/modules/companies/repository.ts +1 -0
- package/src/modules/companies/routes.ts +6 -11
- package/src/modules/companies/schema.ts +3 -3
- package/src/modules/companies/service.ts +6 -10
- package/src/modules/core.ts +2 -0
- package/src/modules/custom-fields/repository.ts +2 -0
- package/src/modules/dashboard/index.ts +1 -0
- package/src/modules/dashboard/repository.ts +64 -0
- package/src/modules/dashboard/routes.ts +15 -0
- package/src/modules/dashboard/service.ts +48 -0
- package/src/modules/decisions/repository.ts +2 -0
- package/src/modules/events/attendances.ts +245 -0
- package/src/modules/events/catalog.ts +36 -0
- package/src/modules/events/index.ts +54 -0
- package/src/modules/events/repository.ts +396 -0
- package/src/modules/events/routes.ts +405 -0
- package/src/modules/events/schema.ts +148 -0
- package/src/modules/events/service.ts +443 -0
- package/src/modules/events/tools.ts +118 -0
- package/src/modules/forms/applySubmissionFields.ts +7 -5
- package/src/modules/forms/fillAddresses.ts +185 -0
- package/src/modules/forms/repository.ts +3 -3
- package/src/modules/forms/routes.ts +2 -2
- package/src/modules/forms/schema.ts +2 -2
- package/src/modules/forms/service.ts +2 -2
- package/src/modules/forms/submission.ts +34 -4
- package/src/modules/hiring/roles.ts +2 -1
- package/src/modules/import-export/addresses.ts +150 -0
- package/src/modules/import-export/drafts.ts +7 -9
- package/src/modules/import-export/exportRows.ts +3 -3
- package/src/modules/import-export/plan.ts +30 -2
- package/src/modules/import-export/presets.ts +55 -7
- package/src/modules/import-export/repository.ts +5 -7
- package/src/modules/import-export/writes.ts +8 -4
- package/src/modules/people/repository.ts +1 -0
- package/src/modules/people/routes.ts +6 -6
- package/src/modules/people/schema.ts +7 -5
- package/src/modules/people/service.ts +7 -5
- package/src/modules/plans/repository.ts +2 -2
- package/src/modules/plans/routes.ts +6 -6
- package/src/modules/plans/schema.ts +4 -4
- package/src/modules/plans/service.ts +3 -3
- package/src/modules/plans/tools.ts +2 -2
- package/src/modules/recordTargets.ts +14 -0
- package/src/modules/references.ts +2 -0
- package/src/modules/sample-data/fixture.ts +208 -27
- package/src/modules/sample-data/index.ts +7 -2
- package/src/modules/sample-data/routes.ts +2 -0
- package/src/modules/sample-data/service.ts +166 -49
- package/src/modules/sample-data/tools.ts +4 -3
- package/src/modules/search/index.ts +2 -1
- package/src/modules/search/repository.ts +32 -1
- package/src/modules/search/service.ts +6 -4
- package/src/modules/smtp-email/index.ts +32 -10
- package/src/modules/workspace/repository.ts +18 -0
- package/src/modules/workspace/routes.ts +16 -0
- package/src/modules/workspace/service.ts +59 -14
- package/src/modules/workspace/starters.ts +275 -14
- package/src/modules/workspace/workspaceAccessMiddleware.ts +3 -4
- package/src/runtime/events.ts +2 -0
- package/src/runtime/module.ts +78 -1
- package/src/runtime/registry.ts +176 -8
- package/src/schema/index.ts +1 -0
- package/src/testing/app.ts +14 -1
- package/src/testing/client.ts +11 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { APP_LINK_PATHS, buildAppLink } from '../../lib/appUrl.ts'
|
|
2
2
|
import type { EmailSender } from '../../lib/email.ts'
|
|
3
3
|
import { renderEmail } from '../../lib/emailContent.ts'
|
|
4
|
-
import { AppError } from '../../lib/errors.ts'
|
|
4
|
+
import { AppError, ExternalSignInError } from '../../lib/errors.ts'
|
|
5
5
|
import type { IdFactory } from '../../lib/ids.ts'
|
|
6
6
|
import { UNIQUE_VIOLATION, postgresErrorCode } from '../../lib/database.ts'
|
|
7
7
|
import type { Database } from '../../lib/database.ts'
|
|
8
8
|
import { MINIMUM_PASSWORD_LENGTH, hashPassword, verifyPassword } from '../../lib/passwords.ts'
|
|
9
9
|
import { generateToken, hashToken } from '../../lib/tokens.ts'
|
|
10
|
+
import type { VerifiedIdentity } from '../../runtime/module.ts'
|
|
10
11
|
import type { TransactionScope } from '../../runtime/transaction.ts'
|
|
12
|
+
import type { MemberRole } from '../workspace/roles.ts'
|
|
13
|
+
import { parseMemberRole } from '../workspace/roles.ts'
|
|
11
14
|
import type { SessionActor } from './actor.ts'
|
|
12
15
|
import { DEFAULT_PREFERENCES, applyPreferenceChanges } from './preferences.ts'
|
|
13
16
|
import type { PreferenceChanges, PreferenceValues } from './preferences.ts'
|
|
@@ -35,6 +38,13 @@ export interface AuthDependencies {
|
|
|
35
38
|
readonly now: () => Date
|
|
36
39
|
/** The deployment's own base URL. Every emailed link is built from it. */
|
|
37
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
|
|
38
48
|
/** Injected only so tests can pin tokens. Production uses the crypto default. */
|
|
39
49
|
readonly newToken?: () => string
|
|
40
50
|
}
|
|
@@ -54,12 +64,37 @@ export interface IssuedSession {
|
|
|
54
64
|
readonly activeWorkspaceId: string | null
|
|
55
65
|
}
|
|
56
66
|
|
|
67
|
+
/** What `completeExternalSignIn` returns: an issued session plus whether it made the account. */
|
|
68
|
+
export interface ExternalIssuedSession extends IssuedSession {
|
|
69
|
+
readonly created: boolean
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
export interface SessionView {
|
|
58
73
|
readonly id: string
|
|
59
74
|
readonly device: string | null
|
|
60
75
|
readonly location: string | null
|
|
61
76
|
readonly lastActiveAt: Date
|
|
62
77
|
readonly current: boolean
|
|
78
|
+
/** The module that signed this session in, or null for a password sign-in. */
|
|
79
|
+
readonly signedInVia: string | null
|
|
80
|
+
}
|
|
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
|
|
63
98
|
}
|
|
64
99
|
|
|
65
100
|
export interface SignUpInput {
|
|
@@ -85,6 +120,12 @@ export interface LogInInput {
|
|
|
85
120
|
readonly location?: string
|
|
86
121
|
}
|
|
87
122
|
|
|
123
|
+
/** A `VerifiedIdentity` plus what the request itself says about the client. */
|
|
124
|
+
export interface ExternalSignInInput extends VerifiedIdentity {
|
|
125
|
+
readonly device?: string
|
|
126
|
+
readonly location?: string
|
|
127
|
+
}
|
|
128
|
+
|
|
88
129
|
function toAccountView(user: repository.UserRecord): AccountView {
|
|
89
130
|
return { id: user.id, email: user.email, name: user.name, emailVerified: user.emailVerifiedAt !== null }
|
|
90
131
|
}
|
|
@@ -105,6 +146,16 @@ function normaliseEmail(email: string): string {
|
|
|
105
146
|
return email.trim().toLowerCase()
|
|
106
147
|
}
|
|
107
148
|
|
|
149
|
+
/**
|
|
150
|
+
* The name to use when a provider sends none. `users.name` is not nullable and
|
|
151
|
+
* an address is the only other thing an identity is guaranteed to carry.
|
|
152
|
+
*/
|
|
153
|
+
function localPartOf(email: string): string {
|
|
154
|
+
const [local] = email.split('@')
|
|
155
|
+
|
|
156
|
+
return local === undefined || local.length === 0 ? email : local
|
|
157
|
+
}
|
|
158
|
+
|
|
108
159
|
/**
|
|
109
160
|
* Rejects a field that is only whitespace.
|
|
110
161
|
*
|
|
@@ -128,6 +179,12 @@ export interface AuthService {
|
|
|
128
179
|
/** Creates the account only. The first workspace comes from onboarding. */
|
|
129
180
|
signUp(input: SignUpInput): Promise<IssuedSession>
|
|
130
181
|
logIn(input: LogInInput): Promise<IssuedSession>
|
|
182
|
+
/**
|
|
183
|
+
* Signs in an identity a module verified elsewhere, provisioning the account
|
|
184
|
+
* when the module asked for it. Never sends a verification email: the
|
|
185
|
+
* provider already proved control of the address.
|
|
186
|
+
*/
|
|
187
|
+
completeExternalSignIn(input: ExternalSignInInput): Promise<ExternalIssuedSession>
|
|
131
188
|
logOut(actor: SessionActor): Promise<void>
|
|
132
189
|
getAccount(actor: SessionActor): Promise<AccountView>
|
|
133
190
|
updateAccount(actor: SessionActor, changes: UpdateAccountChanges): Promise<AccountView>
|
|
@@ -136,6 +193,10 @@ export interface AuthService {
|
|
|
136
193
|
updatePreferences(actor: SessionActor, changes: PreferenceChanges): Promise<PreferenceValues>
|
|
137
194
|
listSessions(actor: SessionActor): Promise<readonly SessionView[]>
|
|
138
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>
|
|
139
200
|
/** Resolves whether or not the address is registered. */
|
|
140
201
|
requestPasswordReset(email: string): Promise<void>
|
|
141
202
|
confirmPasswordReset(token: string, newPassword: string): Promise<void>
|
|
@@ -167,6 +228,8 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
167
228
|
user: repository.UserRecord,
|
|
168
229
|
device: string | undefined,
|
|
169
230
|
location: string | undefined,
|
|
231
|
+
/** The module that verified the identity, or null for a password sign-in. */
|
|
232
|
+
signedInVia: string | null = null,
|
|
170
233
|
): Promise<IssuedSession> {
|
|
171
234
|
const now = dependencies.now()
|
|
172
235
|
const token = newToken()
|
|
@@ -180,6 +243,7 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
180
243
|
tokenHash: hashToken(token),
|
|
181
244
|
device: device ?? null,
|
|
182
245
|
location: location ?? null,
|
|
246
|
+
signedInVia,
|
|
183
247
|
lastActiveAt: now,
|
|
184
248
|
expiresAt,
|
|
185
249
|
})
|
|
@@ -217,6 +281,10 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
217
281
|
return {
|
|
218
282
|
/** Creates the account only. The first workspace comes from onboarding. */
|
|
219
283
|
async signUp(input: SignUpInput): Promise<IssuedSession> {
|
|
284
|
+
if (!dependencies.signupsEnabled) {
|
|
285
|
+
throw new AppError('forbidden', 'Signups are closed on this instance')
|
|
286
|
+
}
|
|
287
|
+
|
|
220
288
|
if (input.password.length < MINIMUM_PASSWORD_LENGTH) {
|
|
221
289
|
throw AppError.validationFailed('Password is too short', [
|
|
222
290
|
{ field: 'password', message: `Must be at least ${MINIMUM_PASSWORD_LENGTH} characters` },
|
|
@@ -269,17 +337,119 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
269
337
|
async logIn(input: LogInInput): Promise<IssuedSession> {
|
|
270
338
|
const user = await repository.findUserByEmail(dependencies.db, normaliseEmail(input.email))
|
|
271
339
|
|
|
272
|
-
// Hash a throwaway password when the
|
|
273
|
-
//
|
|
340
|
+
// Hash a throwaway password when the account is unknown or has no
|
|
341
|
+
// password, so all three cases take the same time to answer.
|
|
274
342
|
const storedHash = user?.passwordHash ?? (await hashPassword('not-a-real-password-placeholder'))
|
|
275
|
-
|
|
276
|
-
|
|
343
|
+
// Always run the verify, even when the hash is the placeholder: skipping
|
|
344
|
+
// it would make a passwordless account answer faster than a wrong one.
|
|
345
|
+
const matches = await verifyPassword(storedHash, input.password)
|
|
346
|
+
|
|
347
|
+
// The null check is the guard, not the placeholder hash. Without it,
|
|
348
|
+
// whoever guessed the placeholder string would sign in to every account
|
|
349
|
+
// that has no password.
|
|
350
|
+
if (user === undefined || user.passwordHash === null || !matches) {
|
|
277
351
|
throw AppError.unauthorized('Email or password is incorrect')
|
|
278
352
|
}
|
|
279
353
|
|
|
280
354
|
return dependencies.transaction(({ tx }) => issueSession(tx, user, input.device, input.location))
|
|
281
355
|
},
|
|
282
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Signs in an identity a module already verified.
|
|
359
|
+
*
|
|
360
|
+
* Core does not redo the verification and never learns the protocol. What
|
|
361
|
+
* it owns is everything downstream: finding or provisioning the account,
|
|
362
|
+
* issuing the session, and recording which module vouched for it.
|
|
363
|
+
*/
|
|
364
|
+
async completeExternalSignIn(input: ExternalSignInInput): Promise<ExternalIssuedSession> {
|
|
365
|
+
// An unverified address is not an identity. A provider that does not say
|
|
366
|
+
// it checked is treated as not having checked.
|
|
367
|
+
if (!input.emailVerified) {
|
|
368
|
+
throw new ExternalSignInError('email_unverified')
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const email = requireText(normaliseEmail(input.email), 'email')
|
|
372
|
+
const name = requireText(input.name ?? localPartOf(email), 'name')
|
|
373
|
+
const now = dependencies.now()
|
|
374
|
+
|
|
375
|
+
const link = async (
|
|
376
|
+
tx: repository.Queryable,
|
|
377
|
+
user: repository.UserRecord,
|
|
378
|
+
): Promise<ExternalIssuedSession> => {
|
|
379
|
+
// Signing in through a provider on an account whose address was never
|
|
380
|
+
// confirmed is the pre-registration takeover case: somebody registered
|
|
381
|
+
// this address with a password and never proved they own it. The
|
|
382
|
+
// provider just proved the opposite. Drop that password and every
|
|
383
|
+
// session it opened; the real owner sets a new one through a reset.
|
|
384
|
+
if (user.emailVerifiedAt === null) {
|
|
385
|
+
await repository.updateUserPassword(tx, user.id, null, now)
|
|
386
|
+
await repository.deleteAllSessionsForUser(tx, user.id)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// The provider proved control of the address, which is the same thing
|
|
390
|
+
// accepting an invite proves.
|
|
391
|
+
await repository.markEmailVerified(tx, user.id, now)
|
|
392
|
+
|
|
393
|
+
const issued = await issueSession(
|
|
394
|
+
tx,
|
|
395
|
+
{ ...user, emailVerifiedAt: user.emailVerifiedAt ?? now },
|
|
396
|
+
input.device,
|
|
397
|
+
input.location,
|
|
398
|
+
input.verifiedBy,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
return { ...issued, created: false }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const existing = await repository.findUserByEmail(dependencies.db, email)
|
|
405
|
+
|
|
406
|
+
if (existing !== undefined) {
|
|
407
|
+
return dependencies.transaction(({ tx }) => link(tx, existing))
|
|
408
|
+
}
|
|
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
|
+
|
|
416
|
+
if (input.provision === 'refuse') {
|
|
417
|
+
throw new ExternalSignInError('unknown_identity')
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return dependencies.transaction(async ({ tx }) => {
|
|
421
|
+
let user: repository.UserRecord
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
user = await repository.insertUser(tx, {
|
|
425
|
+
id: dependencies.createId('user'),
|
|
426
|
+
email,
|
|
427
|
+
name,
|
|
428
|
+
passwordHash: null,
|
|
429
|
+
emailVerifiedAt: now,
|
|
430
|
+
})
|
|
431
|
+
} catch (error: unknown) {
|
|
432
|
+
if (postgresErrorCode(error) !== UNIQUE_VIOLATION) {
|
|
433
|
+
throw error
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Two first sign-ins for the same address at once. The other one won;
|
|
437
|
+
// this is now an ordinary link.
|
|
438
|
+
const raced = await repository.findUserByEmail(tx, email)
|
|
439
|
+
|
|
440
|
+
if (raced === undefined) {
|
|
441
|
+
throw error
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return link(tx, raced)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const issued = await issueSession(tx, user, input.device, input.location, input.verifiedBy)
|
|
448
|
+
|
|
449
|
+
return { ...issued, created: true }
|
|
450
|
+
})
|
|
451
|
+
},
|
|
452
|
+
|
|
283
453
|
async logOut(actor: SessionActor): Promise<void> {
|
|
284
454
|
await repository.deleteSession(dependencies.db, actor.userId, actor.sessionId)
|
|
285
455
|
},
|
|
@@ -297,30 +467,45 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
297
467
|
* password reset. The current password must verify, every other session
|
|
298
468
|
* ends, and the address being replaced is told, in case the caller was not
|
|
299
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.
|
|
300
477
|
*/
|
|
301
478
|
async updateAccount(actor: SessionActor, changes: UpdateAccountChanges): Promise<AccountView> {
|
|
302
479
|
const user = await requireUser(actor.userId)
|
|
303
480
|
|
|
481
|
+
const nextEmail =
|
|
482
|
+
changes.email === undefined ? undefined : requireText(normaliseEmail(changes.email), 'email')
|
|
483
|
+
const emailChanging = nextEmail !== undefined && nextEmail !== user.email
|
|
484
|
+
|
|
304
485
|
const values = {
|
|
305
486
|
...(changes.name === undefined ? {} : { name: requireText(changes.name, 'name') }),
|
|
306
|
-
...(
|
|
307
|
-
|
|
308
|
-
: { email: requireText(normaliseEmail(changes.email), 'email') }),
|
|
487
|
+
...(nextEmail === undefined ? {} : { email: nextEmail }),
|
|
488
|
+
...(emailChanging ? { emailVerifiedAt: null } : {}),
|
|
309
489
|
}
|
|
310
490
|
|
|
491
|
+
// A passwordless account cannot prove itself this way. It sets a password
|
|
492
|
+
// through the reset flow first, which is the same bar this check is.
|
|
311
493
|
if (
|
|
312
494
|
changes.email !== undefined &&
|
|
313
495
|
(changes.currentPassword === undefined ||
|
|
496
|
+
user.passwordHash === null ||
|
|
314
497
|
!(await verifyPassword(user.passwordHash, changes.currentPassword)))
|
|
315
498
|
) {
|
|
316
499
|
throw AppError.unauthorized('Current password is incorrect')
|
|
317
500
|
}
|
|
318
501
|
|
|
319
502
|
const previousEmail = user.email
|
|
503
|
+
const now = dependencies.now()
|
|
504
|
+
const verificationToken = emailChanging ? newToken() : undefined
|
|
320
505
|
|
|
321
506
|
try {
|
|
322
507
|
const updated = await dependencies.transaction(async ({ tx }) => {
|
|
323
|
-
const saved = await repository.updateUserProfile(tx, actor.userId, values,
|
|
508
|
+
const saved = await repository.updateUserProfile(tx, actor.userId, values, now)
|
|
324
509
|
|
|
325
510
|
if (saved === undefined) {
|
|
326
511
|
throw AppError.unauthorized('This session no longer belongs to an account')
|
|
@@ -330,6 +515,15 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
330
515
|
await repository.deleteOtherSessionsForUser(tx, actor.userId, actor.sessionId)
|
|
331
516
|
}
|
|
332
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
|
+
|
|
333
527
|
return saved
|
|
334
528
|
})
|
|
335
529
|
|
|
@@ -349,6 +543,13 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
349
543
|
})
|
|
350
544
|
}
|
|
351
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
|
+
|
|
352
553
|
return toAccountView(updated)
|
|
353
554
|
} catch (error: unknown) {
|
|
354
555
|
if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
|
|
@@ -405,6 +606,7 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
405
606
|
location: record.location,
|
|
406
607
|
lastActiveAt: record.lastActiveAt,
|
|
407
608
|
current: record.id === actor.sessionId,
|
|
609
|
+
signedInVia: record.signedInVia,
|
|
408
610
|
}))
|
|
409
611
|
},
|
|
410
612
|
|
|
@@ -418,6 +620,51 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
418
620
|
}
|
|
419
621
|
},
|
|
420
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
|
+
|
|
421
668
|
/**
|
|
422
669
|
* Always resolves, whether or not the address is registered. Telling a
|
|
423
670
|
* stranger which addresses have accounts is the whole attack.
|
|
@@ -494,7 +741,11 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
494
741
|
|
|
495
742
|
const user = await repository.findUserById(dependencies.db, actor.userId)
|
|
496
743
|
|
|
497
|
-
if (
|
|
744
|
+
if (
|
|
745
|
+
user === undefined ||
|
|
746
|
+
user.passwordHash === null ||
|
|
747
|
+
!(await verifyPassword(user.passwordHash, currentPassword))
|
|
748
|
+
) {
|
|
498
749
|
throw AppError.unauthorized('Current password is incorrect')
|
|
499
750
|
}
|
|
500
751
|
|
|
@@ -11,6 +11,20 @@ import type { Context } from 'hono'
|
|
|
11
11
|
|
|
12
12
|
export const SESSION_COOKIE = 'kelpie_session'
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The device and location shown on the Security page come from the request
|
|
16
|
+
* itself.
|
|
17
|
+
*
|
|
18
|
+
* Here rather than in `routes.ts` because the external sign-in handler needs
|
|
19
|
+
* the same description, and a session created by a module should look the same
|
|
20
|
+
* on that page as one created by the login form.
|
|
21
|
+
*/
|
|
22
|
+
export function describeClient(context: Context): { device?: string; location?: string } {
|
|
23
|
+
const userAgent = context.req.header('User-Agent')
|
|
24
|
+
|
|
25
|
+
return userAgent === undefined ? {} : { device: userAgent }
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
/** Thirty days, matching the session row's expiry. */
|
|
15
29
|
const COOKIE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60
|
|
16
30
|
|
|
@@ -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
|
/**
|