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