@headless-lms/core 0.3.0 → 0.5.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 (307) hide show
  1. package/dist/assets/events.d.ts +24 -18
  2. package/dist/assets/file-urls.d.ts +11 -7
  3. package/dist/assets/file-urls.js +14 -4
  4. package/dist/assets/file-urls.js.map +1 -1
  5. package/dist/assets/index.d.ts +5 -4
  6. package/dist/assets/index.js +4 -3
  7. package/dist/assets/jobs.d.ts +15 -0
  8. package/dist/assets/jobs.js +30 -0
  9. package/dist/assets/jobs.js.map +1 -0
  10. package/dist/assets/ports.d.ts +37 -17
  11. package/dist/assets/service.d.ts +30 -15
  12. package/dist/assets/service.js +229 -59
  13. package/dist/assets/service.js.map +1 -1
  14. package/dist/automations/actions.js +2 -63
  15. package/dist/automations/actions.js.map +1 -1
  16. package/dist/automations/catalog.d.ts +1 -2
  17. package/dist/automations/catalog.js +137 -65
  18. package/dist/automations/catalog.js.map +1 -1
  19. package/dist/automations/community-derivations.js +20 -20
  20. package/dist/automations/community-derivations.js.map +1 -1
  21. package/dist/automations/community-mail.d.ts +36 -20
  22. package/dist/automations/community-mail.js +32 -24
  23. package/dist/automations/community-mail.js.map +1 -1
  24. package/dist/automations/event-permissions.d.ts +12 -0
  25. package/dist/automations/event-permissions.js +38 -0
  26. package/dist/automations/event-permissions.js.map +1 -0
  27. package/dist/automations/events.d.ts +0 -10
  28. package/dist/automations/index.d.ts +12 -4
  29. package/dist/automations/index.js +9 -1
  30. package/dist/automations/notifications/applicable.d.ts +8 -0
  31. package/dist/automations/notifications/applicable.js +12 -0
  32. package/dist/automations/notifications/applicable.js.map +1 -0
  33. package/dist/automations/notifications/debounce.d.ts +25 -0
  34. package/dist/automations/notifications/debounce.js +93 -0
  35. package/dist/automations/notifications/debounce.js.map +1 -0
  36. package/dist/automations/notifications/defaults.d.ts +8 -0
  37. package/dist/automations/notifications/defaults.js +346 -0
  38. package/dist/automations/notifications/defaults.js.map +1 -0
  39. package/dist/automations/notifications/delivery.d.ts +28 -0
  40. package/dist/automations/notifications/delivery.js +88 -0
  41. package/dist/automations/notifications/delivery.js.map +1 -0
  42. package/dist/automations/notifications/processor.d.ts +64 -0
  43. package/dist/automations/notifications/processor.js +270 -0
  44. package/dist/automations/notifications/processor.js.map +1 -0
  45. package/dist/automations/notifications/rules/content.d.ts +6 -0
  46. package/dist/automations/notifications/rules/content.js +96 -0
  47. package/dist/automations/notifications/rules/content.js.map +1 -0
  48. package/dist/automations/notifications/rules/discussion.d.ts +7 -0
  49. package/dist/automations/notifications/rules/discussion.js +678 -0
  50. package/dist/automations/notifications/rules/discussion.js.map +1 -0
  51. package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
  52. package/dist/automations/notifications/rules/entitlements.js +276 -0
  53. package/dist/automations/notifications/rules/entitlements.js.map +1 -0
  54. package/dist/automations/notifications/rules/events.d.ts +9 -0
  55. package/dist/automations/notifications/rules/events.js +27 -0
  56. package/dist/automations/notifications/rules/events.js.map +1 -0
  57. package/dist/automations/notifications/rules/index.d.ts +6 -0
  58. package/dist/automations/notifications/rules/index.js +15 -0
  59. package/dist/automations/notifications/rules/index.js.map +1 -0
  60. package/dist/automations/notifications/rules/organizations.d.ts +6 -0
  61. package/dist/automations/notifications/rules/organizations.js +35 -0
  62. package/dist/automations/notifications/rules/organizations.js.map +1 -0
  63. package/dist/automations/notifications/rules/recipients.d.ts +32 -0
  64. package/dist/automations/notifications/rules/recipients.js +98 -0
  65. package/dist/automations/notifications/rules/recipients.js.map +1 -0
  66. package/dist/automations/notifications/service.d.ts +29 -0
  67. package/dist/automations/notifications/service.js +45 -0
  68. package/dist/automations/notifications/service.js.map +1 -0
  69. package/dist/automations/notifications/types.d.ts +7 -0
  70. package/dist/automations/notifications/types.js +560 -0
  71. package/dist/automations/notifications/types.js.map +1 -0
  72. package/dist/automations/ports.d.ts +134 -21
  73. package/dist/automations/service.d.ts +30 -30
  74. package/dist/automations/service.js +111 -144
  75. package/dist/automations/service.js.map +1 -1
  76. package/dist/content/event-sight.d.ts +11 -0
  77. package/dist/content/event-sight.js +11 -0
  78. package/dist/content/event-sight.js.map +1 -0
  79. package/dist/content/events.d.ts +437 -46
  80. package/dist/content/events.js +45 -1
  81. package/dist/content/events.js.map +1 -1
  82. package/dist/content/index.d.ts +8 -4
  83. package/dist/content/index.js +5 -1
  84. package/dist/content/jobs.d.ts +16 -0
  85. package/dist/content/jobs.js +37 -0
  86. package/dist/content/jobs.js.map +1 -0
  87. package/dist/content/model.d.ts +2 -2
  88. package/dist/content/notification-categories.d.ts +37 -0
  89. package/dist/content/notification-categories.js +73 -0
  90. package/dist/content/notification-categories.js.map +1 -0
  91. package/dist/content/ports.d.ts +132 -68
  92. package/dist/content/service.d.ts +100 -68
  93. package/dist/content/service.js +703 -146
  94. package/dist/content/service.js.map +1 -1
  95. package/dist/content/titles.d.ts +25 -0
  96. package/dist/content/titles.js +82 -0
  97. package/dist/content/titles.js.map +1 -0
  98. package/dist/content/types.d.ts +2 -2
  99. package/dist/discussion/authors.d.ts +4 -2
  100. package/dist/discussion/authors.js +9 -5
  101. package/dist/discussion/authors.js.map +1 -1
  102. package/dist/discussion/chats.d.ts +63 -39
  103. package/dist/discussion/chats.js +455 -190
  104. package/dist/discussion/chats.js.map +1 -1
  105. package/dist/discussion/digests.d.ts +24 -0
  106. package/dist/discussion/digests.js +104 -0
  107. package/dist/discussion/digests.js.map +1 -0
  108. package/dist/discussion/events.d.ts +227 -23
  109. package/dist/discussion/events.js +114 -19
  110. package/dist/discussion/events.js.map +1 -1
  111. package/dist/discussion/index.d.ts +12 -4
  112. package/dist/discussion/index.js +9 -2
  113. package/dist/discussion/jobs.d.ts +16 -0
  114. package/dist/discussion/jobs.js +40 -0
  115. package/dist/discussion/jobs.js.map +1 -0
  116. package/dist/discussion/live.d.ts +106 -0
  117. package/dist/discussion/live.js +642 -0
  118. package/dist/discussion/live.js.map +1 -0
  119. package/dist/discussion/mentions.d.ts +11 -0
  120. package/dist/discussion/mentions.js +35 -0
  121. package/dist/discussion/mentions.js.map +1 -0
  122. package/dist/discussion/ports.d.ts +279 -114
  123. package/dist/discussion/ports.js.map +1 -1
  124. package/dist/discussion/posts.d.ts +60 -44
  125. package/dist/discussion/posts.js +453 -287
  126. package/dist/discussion/posts.js.map +1 -1
  127. package/dist/discussion/rules.d.ts +18 -0
  128. package/dist/discussion/rules.js +22 -0
  129. package/dist/discussion/rules.js.map +1 -0
  130. package/dist/discussion/service.d.ts +50 -35
  131. package/dist/discussion/service.js +333 -205
  132. package/dist/discussion/service.js.map +1 -1
  133. package/dist/discussion/subscriptions.d.ts +37 -0
  134. package/dist/discussion/subscriptions.js +115 -0
  135. package/dist/discussion/subscriptions.js.map +1 -0
  136. package/dist/discussion/titles.d.ts +28 -0
  137. package/dist/discussion/titles.js +98 -0
  138. package/dist/discussion/titles.js.map +1 -0
  139. package/dist/entitlements/events.d.ts +20 -20
  140. package/dist/entitlements/index.d.ts +5 -4
  141. package/dist/entitlements/index.js +3 -2
  142. package/dist/entitlements/jobs.d.ts +12 -0
  143. package/dist/entitlements/jobs.js +18 -0
  144. package/dist/entitlements/jobs.js.map +1 -0
  145. package/dist/entitlements/model.d.ts +2 -2
  146. package/dist/entitlements/ports.d.ts +68 -38
  147. package/dist/entitlements/rules.d.ts +3 -6
  148. package/dist/entitlements/rules.js +5 -10
  149. package/dist/entitlements/rules.js.map +1 -1
  150. package/dist/entitlements/service.d.ts +43 -28
  151. package/dist/entitlements/service.js +387 -160
  152. package/dist/entitlements/service.js.map +1 -1
  153. package/dist/identity/devices.d.ts +30 -0
  154. package/dist/identity/devices.js +109 -0
  155. package/dist/identity/devices.js.map +1 -0
  156. package/dist/identity/index.d.ts +6 -5
  157. package/dist/identity/index.js +4 -3
  158. package/dist/identity/model.d.ts +2 -2
  159. package/dist/identity/notification-settings.d.ts +28 -0
  160. package/dist/identity/notification-settings.js +114 -0
  161. package/dist/identity/notification-settings.js.map +1 -0
  162. package/dist/identity/ports.d.ts +80 -56
  163. package/dist/identity/service.d.ts +8 -7
  164. package/dist/identity/service.js +17 -13
  165. package/dist/identity/service.js.map +1 -1
  166. package/dist/integrations/ports.d.ts +9 -8
  167. package/dist/integrations/service.d.ts +9 -8
  168. package/dist/integrations/service.js +33 -16
  169. package/dist/integrations/service.js.map +1 -1
  170. package/dist/organizations/default-roles.d.ts +22 -0
  171. package/dist/organizations/default-roles.js +89 -0
  172. package/dist/organizations/default-roles.js.map +1 -0
  173. package/dist/organizations/effective-permissions.d.ts +45 -0
  174. package/dist/organizations/effective-permissions.js +113 -0
  175. package/dist/organizations/effective-permissions.js.map +1 -0
  176. package/dist/organizations/events.d.ts +320 -181
  177. package/dist/organizations/events.js +90 -18
  178. package/dist/organizations/events.js.map +1 -1
  179. package/dist/organizations/index.d.ts +15 -10
  180. package/dist/organizations/index.js +8 -3
  181. package/dist/organizations/members.d.ts +66 -8
  182. package/dist/organizations/members.js.map +1 -1
  183. package/dist/organizations/notification-catalog.d.ts +31 -0
  184. package/dist/organizations/notification-catalog.js +32 -0
  185. package/dist/organizations/notification-catalog.js.map +1 -0
  186. package/dist/organizations/notification-categories.d.ts +34 -0
  187. package/dist/organizations/notification-categories.js +66 -0
  188. package/dist/organizations/notification-categories.js.map +1 -0
  189. package/dist/organizations/org-roles.d.ts +33 -0
  190. package/dist/organizations/org-roles.js +49 -0
  191. package/dist/organizations/org-roles.js.map +1 -0
  192. package/dist/organizations/permissions.d.ts +311 -0
  193. package/dist/organizations/permissions.js +327 -0
  194. package/dist/organizations/permissions.js.map +1 -0
  195. package/dist/organizations/ports.d.ts +187 -65
  196. package/dist/organizations/service.d.ts +104 -35
  197. package/dist/organizations/service.js +1247 -323
  198. package/dist/organizations/service.js.map +1 -1
  199. package/dist/organizations/testing.d.ts +18 -0
  200. package/dist/organizations/testing.js +37 -0
  201. package/dist/organizations/testing.js.map +1 -0
  202. package/dist/organizations/types.d.ts +5 -16
  203. package/dist/progress/index.d.ts +2 -2
  204. package/dist/progress/ports.d.ts +11 -3
  205. package/dist/progress/service.d.ts +6 -5
  206. package/dist/progress/service.js +23 -8
  207. package/dist/progress/service.js.map +1 -1
  208. package/dist/reporting/admin/index.d.ts +6 -4
  209. package/dist/reporting/admin/model.d.ts +36 -3
  210. package/dist/reporting/admin/ports.d.ts +39 -14
  211. package/dist/reporting/admin/service.d.ts +32 -11
  212. package/dist/reporting/admin/service.js +102 -18
  213. package/dist/reporting/admin/service.js.map +1 -1
  214. package/dist/reporting/events/index.d.ts +27 -0
  215. package/dist/reporting/events/index.js +20 -0
  216. package/dist/reporting/events/index.js.map +1 -0
  217. package/dist/reporting/member/calendar.d.ts +11 -0
  218. package/dist/reporting/member/calendar.js +75 -0
  219. package/dist/reporting/member/calendar.js.map +1 -0
  220. package/dist/reporting/member/index.d.ts +3 -2
  221. package/dist/reporting/member/model.d.ts +80 -26
  222. package/dist/reporting/member/ports.d.ts +50 -28
  223. package/dist/reporting/member/service.d.ts +81 -35
  224. package/dist/reporting/member/service.js +438 -145
  225. package/dist/reporting/member/service.js.map +1 -1
  226. package/dist/shared/actor.d.ts +22 -3
  227. package/dist/shared/errors.d.ts +15 -2
  228. package/dist/shared/errors.js +21 -2
  229. package/dist/shared/errors.js.map +1 -1
  230. package/dist/shared/id.d.ts +7 -0
  231. package/dist/shared/id.js +8 -1
  232. package/dist/shared/id.js.map +1 -1
  233. package/dist/shared/logger.d.ts +2 -1
  234. package/dist/shared/logger.js +10 -1
  235. package/dist/shared/logger.js.map +1 -1
  236. package/dist/shared/mailer.d.ts +11 -1
  237. package/dist/shared/mailer.js +32 -5
  238. package/dist/shared/mailer.js.map +1 -1
  239. package/dist/shared/notification-categories.d.ts +16 -0
  240. package/dist/shared/notification-categories.js +94 -0
  241. package/dist/shared/notification-categories.js.map +1 -0
  242. package/dist/shared/notification-settings.d.ts +14 -0
  243. package/dist/shared/notification-settings.js +61 -0
  244. package/dist/shared/notification-settings.js.map +1 -0
  245. package/dist/shared/ports.d.ts +68 -2
  246. package/dist/shared/settings.d.ts +2 -1
  247. package/dist/shared/settings.js +1 -0
  248. package/dist/shared/settings.js.map +1 -1
  249. package/dist/types/automations.d.ts +8 -2
  250. package/dist/types/content.d.ts +2 -2
  251. package/dist/types/discussion.d.ts +4 -5
  252. package/dist/types/email-templates.d.ts +119 -6
  253. package/dist/types/entitlements.d.ts +2 -2
  254. package/dist/types/identity.d.ts +14 -17
  255. package/dist/types/index.d.ts +13 -12
  256. package/dist/types/index.js +2 -1
  257. package/dist/types/notifications.d.ts +157 -0
  258. package/dist/types/notifications.js +1 -0
  259. package/dist/types/organizations.d.ts +3 -2
  260. package/dist/types/ports.d.ts +8 -1
  261. package/dist/types/ports.js +7 -1
  262. package/dist/types/ports.js.map +1 -0
  263. package/dist/types/schemas/assets.d.ts +5 -5
  264. package/dist/types/schemas/assets.js +4 -3
  265. package/dist/types/schemas/assets.js.map +1 -1
  266. package/dist/types/schemas/automations.d.ts +1 -10
  267. package/dist/types/schemas/automations.js +1 -3
  268. package/dist/types/schemas/automations.js.map +1 -1
  269. package/dist/types/schemas/content.d.ts +596 -11
  270. package/dist/types/schemas/content.js +225 -9
  271. package/dist/types/schemas/content.js.map +1 -1
  272. package/dist/types/schemas/discussion.d.ts +362 -66
  273. package/dist/types/schemas/discussion.js +123 -15
  274. package/dist/types/schemas/discussion.js.map +1 -1
  275. package/dist/types/schemas/entitlements.d.ts +14 -1
  276. package/dist/types/schemas/entitlements.js +11 -4
  277. package/dist/types/schemas/entitlements.js.map +1 -1
  278. package/dist/types/schemas/identity.d.ts +1 -85
  279. package/dist/types/schemas/identity.js +1 -42
  280. package/dist/types/schemas/identity.js.map +1 -1
  281. package/dist/types/schemas/index.d.ts +8 -7
  282. package/dist/types/schemas/index.js +8 -7
  283. package/dist/types/schemas/notifications.d.ts +96 -0
  284. package/dist/types/schemas/notifications.js +57 -0
  285. package/dist/types/schemas/notifications.js.map +1 -0
  286. package/dist/types/schemas/organizations.d.ts +96 -60
  287. package/dist/types/schemas/organizations.js +78 -27
  288. package/dist/types/schemas/organizations.js.map +1 -1
  289. package/dist/types/schemas/progress.d.ts +0 -1
  290. package/dist/types/schemas/progress.js +0 -1
  291. package/dist/types/schemas/progress.js.map +1 -1
  292. package/package.json +1 -1
  293. package/dist/automations/community-notifications.d.ts +0 -16
  294. package/dist/automations/community-notifications.js +0 -84
  295. package/dist/automations/community-notifications.js.map +0 -1
  296. package/dist/automations/seeds.d.ts +0 -17
  297. package/dist/automations/seeds.js +0 -235
  298. package/dist/automations/seeds.js.map +0 -1
  299. package/dist/identity/notifications.d.ts +0 -27
  300. package/dist/identity/notifications.js +0 -101
  301. package/dist/identity/notifications.js.map +0 -1
  302. package/dist/organizations/access-scopes.d.ts +0 -19
  303. package/dist/organizations/access-scopes.js +0 -89
  304. package/dist/organizations/access-scopes.js.map +0 -1
  305. package/dist/organizations/roles.d.ts +0 -19
  306. package/dist/organizations/roles.js +0 -82
  307. package/dist/organizations/roles.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","names":[],"sources":["../../src/entitlements/service.ts"],"sourcesContent":["// entitlements context — service implementation (inbound port).\n//\n// Mutations run inside the context's UnitOfWork: the domain write and the\n// outbox append commit in ONE transaction (transactional outbox). This\n// service never publishes — the outbox relay republishes committed events to\n// the EventBus at-least-once.\nimport type { CommunityReader, Space, SpaceRowLock } from '../content/index.js';\nimport type {\n ContentType,\n Entitlement,\n EntitlementsQuery,\n GrantEntitlementInput,\n Page,\n SpaceMember,\n} from './model.js';\nimport type {\n AccessFacts,\n EntitlementsRepository,\n EntitlementsService,\n EntitlementsUnitOfWork,\n EndingGrantKey,\n GrantTarget,\n InsertSpaceMemberInput,\n OpenableSpace,\n SpaceAccessRule,\n} from './ports.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { Logger, NewDomainEvent, OutboxAppender } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { ConflictError, ForbiddenError, NotFoundError } from '../shared/errors.js';\nimport { canModerate, isStaffRole } from '../organizations/index.js';\nimport { entitlementEvents } from './events.js';\nimport { laterExpiry, opensSpace, seesSpace } from './rules.js';\n\nconst ENDING_SOON_MS = 7 * 24 * 60 * 60 * 1000;\nconst NOTICE_BATCH_SIZE = 100;\n\nexport type EntitlementsServiceParams = {\n /** Read-only access (list) — runs outside any transaction. */\n repo: EntitlementsRepository;\n content: CommunityReader;\n /** Atomic write scope: tx-bound repo + outbox appender. */\n uow: EntitlementsUnitOfWork;\n logger?: Logger;\n};\n\nexport class EntitlementsServiceImpl implements EntitlementsService {\n private readonly repo: EntitlementsRepository;\n private readonly content: CommunityReader;\n private readonly uow: EntitlementsUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: EntitlementsServiceParams) {\n this.repo = params.repo;\n this.content = params.content;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n list(orgId: string, query: EntitlementsQuery): Promise<Page<Entitlement>> {\n return this.repo.list(orgId, query);\n }\n\n async grant(orgId: string, input: GrantEntitlementInput): Promise<Entitlement> {\n const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {\n const created = await entitlements.insert(orgId, input);\n await outbox.append([\n entitlementEvents.entitlementCreated.make({\n orgId,\n data: created,\n }),\n ]);\n return created;\n });\n this.logger.info('entitlement granted', {\n orgId,\n entitlementId: entitlement.id,\n orgUserId: entitlement.orgUserId,\n bundleId: entitlement.bundleId,\n contentId: entitlement.contentId,\n });\n return entitlement;\n }\n\n async ensureGrant(orgId: string, input: GrantEntitlementInput): Promise<Entitlement> {\n const { entitlement, change } = await this.uow.run(async ({ entitlements, outbox }) => {\n const created = await entitlements.insertIfAbsent(orgId, input);\n if (created) {\n await outbox.append([entitlementEvents.entitlementCreated.make({ orgId, data: created })]);\n return { entitlement: created, change: 'created' as const };\n }\n const existing = await entitlements.lockGrant(orgId, input);\n if (!existing) {\n throw new ConflictError('The grant changed while it was being given');\n }\n if (existing.status === 'revoked') {\n return { entitlement: existing, change: null };\n }\n const expiresAt = laterExpiry(existing.expiresAt, input.expiresAt ?? null);\n if (expiresAt?.getTime() === existing.expiresAt?.getTime()) {\n return { entitlement: existing, change: null };\n }\n const updated = await entitlements.setExpiresAt(orgId, existing.id, expiresAt);\n if (!updated) {\n throw new NotFoundError('Entitlement', existing.id);\n }\n await outbox.append([entitlementEvents.entitlementUpdated.make({ orgId, data: updated })]);\n return { entitlement: updated, change: 'updated' as const };\n });\n if (change) {\n this.logger.info(change === 'created' ? 'entitlement granted' : 'entitlement extended', {\n orgId,\n entitlementId: entitlement.id,\n orgUserId: entitlement.orgUserId,\n bundleId: entitlement.bundleId,\n contentId: entitlement.contentId,\n });\n }\n return entitlement;\n }\n\n async setStatus(\n orgId: string,\n id: string,\n status: 'active' | 'revoked',\n ): Promise<Entitlement | null> {\n const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {\n const updated = await entitlements.setStatus(orgId, id, status);\n if (!updated) {\n return null;\n }\n await outbox.append([\n status === 'revoked'\n ? entitlementEvents.entitlementDeleted.make({\n orgId,\n data: updated,\n })\n : entitlementEvents.entitlementUpdated.make({\n orgId,\n data: updated,\n }),\n ]);\n return updated;\n });\n if (entitlement) {\n this.logger.info('entitlement status changed', { orgId, entitlementId: id, status });\n }\n return entitlement;\n }\n\n hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean> {\n return this.repo.hasAccess(orgId, orgUserId, contentId);\n }\n\n listAccessibleContent(orgId: string, orgUserId: string, type?: ContentType): Promise<string[]> {\n return this.repo.listAccessibleContent(orgId, orgUserId, type);\n }\n\n async isCommunityMember(orgId: string, actor: Actor, communityId: string): Promise<boolean> {\n const facts = await this.accessOf(orgId, actor.id, communityId, null);\n return facts?.member === true;\n }\n\n async canOpenSpace(orgId: string, actor: Actor, spaceId: string): Promise<boolean> {\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space) {\n return false;\n }\n return this.canOpen(orgId, actor, space);\n }\n\n async canOpen(orgId: string, actor: Actor, space: OpenableSpace): Promise<boolean> {\n const facts = await this.accessOf(orgId, actor.id, space.communityId, space.id);\n if (!facts) {\n return false;\n }\n return opensSpace(facts.role, facts.member, space.accessRule, facts.activeInSpace);\n }\n\n async spaceStandings(\n orgId: string,\n actor: Actor,\n communityId: string,\n ): Promise<Record<string, SpaceMember['status']>> {\n const rows = await this.repo.listSpaceMembersFor(orgId, actor.id, communityId);\n return Object.fromEntries(rows.map((row) => [row.spaceId, row.status]));\n }\n\n async openSpacesFor(orgId: string, actor: Actor, communityId: string): Promise<Space[]> {\n const facts = await this.accessOf(orgId, actor.id, communityId, null);\n if (!facts?.member) {\n return [];\n }\n const [spaces, activeIds] = await Promise.all([\n this.content.listSpaces(orgId, communityId),\n this.repo.activeSpaceIds(orgId, actor.id, communityId),\n ]);\n const active = new Set(activeIds);\n return spaces.filter((space) =>\n opensSpace(facts.role, facts.member, space.accessRule, active.has(space.id)),\n );\n }\n\n async openSpaceIds(orgId: string, actor: Actor, communityId: string): Promise<string[]> {\n const spaces = await this.openSpacesFor(orgId, actor, communityId);\n return spaces.map((space) => space.id);\n }\n\n async communityMembersAmong(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<string[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n const facts = await this.repo.accessAmong(orgId, communityId, null, orgUserIds);\n return facts.filter((fact) => fact.member).map((fact) => fact.orgUserId);\n }\n\n async spaceOpenersAmong(orgId: string, spaceId: string, orgUserIds: string[]): Promise<string[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space) {\n return [];\n }\n const facts = await this.repo.accessAmong(orgId, space.communityId, space.id, orgUserIds);\n return facts\n .filter((fact) => opensSpace(fact.role, fact.member, space.accessRule, fact.activeInSpace))\n .map((fact) => fact.orgUserId);\n }\n\n async spaceOpeners(orgId: string, spaceId: string): Promise<string[]> {\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space) {\n return [];\n }\n const members = await this.repo.communityMemberIds(orgId, space.communityId);\n return this.spaceOpenersAmong(orgId, spaceId, members);\n }\n\n communitiesOpenedBy(orgId: string, grant: GrantTarget): Promise<string[]> {\n return this.repo.communitiesOpenedBy(orgId, grant);\n }\n\n communityModeratorIds(orgId: string, communityId: string): Promise<string[]> {\n return this.repo.communityModeratorIds(orgId, communityId);\n }\n\n communityIdsOpenedFor(orgId: string, orgUserId: string): Promise<string[]> {\n return this.repo.communityIdsOpenedFor(orgId, orgUserId);\n }\n\n countActiveSpaceMembers(orgId: string, spaceId: string): Promise<number> {\n return this.repo.countActiveSpaceMembers(orgId, spaceId);\n }\n\n async requestJoin(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_request');\n if (canModerate(actor.role)) {\n throw new ForbiddenError('Staff do not request a place in a space');\n }\n await this.assertGrant(orgId, actor, actor.id, space.communityId);\n const created = await this.createMember(orgId, actor, space, actor.id, 'requested', 'request');\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId: actor.id,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async invite(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_invite');\n await this.assertCanManage(orgId, actor, space.communityId);\n await this.assertHolds(orgId, actor, orgUserId, space.communityId);\n const created = await this.createMember(orgId, actor, space, orgUserId, 'invited', 'invite');\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async addSpaceMember(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertHasMembers(space.accessRule);\n await this.assertCanManage(orgId, actor, space.communityId);\n await this.assertHolds(orgId, actor, orgUserId, space.communityId);\n const created = await this.uow.run(async (scope) => {\n const locked = await this.lockSpace(scope.spaces, orgId, spaceId);\n this.assertHasMembers(locked.accessRule);\n return this.insertMember(\n scope.entitlements,\n scope.outbox,\n orgId,\n actor,\n {\n communityId: space.communityId,\n spaceId,\n orgUserId,\n status: 'active',\n source: 'manual',\n },\n locked,\n );\n });\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async accept(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceMember> {\n this.assertHolder(actor);\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_invite');\n await this.assertGrant(orgId, actor, actor.id, space.communityId);\n return this.activate(orgId, actor, spaceId, actor.id, 'by_invite', 'invited', () =>\n ownRowMissing('invited'),\n );\n }\n\n async approve(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_request');\n await this.assertCanManage(orgId, actor, space.communityId);\n await this.assertGrant(orgId, actor, orgUserId, space.communityId);\n return this.activate(\n orgId,\n actor,\n spaceId,\n orgUserId,\n 'by_request',\n 'requested',\n () => new NotFoundError('Space member', orgUserId),\n );\n }\n\n async decline(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n await this.assertCanManage(orgId, actor, space.communityId);\n return this.deleteMember(\n orgId,\n actor,\n spaceId,\n orgUserId,\n 'requested',\n () => new NotFoundError('Space member', orgUserId),\n );\n }\n\n async refuse(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertHolder(actor);\n await this.assertGrant(orgId, actor, actor.id, space.communityId);\n return this.deleteMember(orgId, actor, spaceId, actor.id, 'invited', () =>\n ownRowMissing('invited'),\n );\n }\n\n async leave(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertHolder(actor);\n await this.assertGrant(orgId, actor, actor.id, space.communityId);\n return this.deleteMember(orgId, actor, spaceId, actor.id, 'active', () =>\n ownRowMissing('active'),\n );\n }\n\n async remove(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertHasMembers(space.accessRule);\n await this.assertCanManage(orgId, actor, space.communityId);\n const { updated, previousStatus } = await this.uow.run(\n async ({ entitlements, spaces, outbox }) => {\n const locked = await this.lockSpace(spaces, orgId, spaceId);\n this.assertHasMembers(locked.accessRule);\n const row = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (!row) {\n this.logger.warn('space member remove rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw new NotFoundError('Space member', orgUserId);\n }\n if (row.status === 'revoked') {\n throw new ConflictError('This person is already removed from the space');\n }\n const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, 'revoked');\n if (!next) {\n throw new NotFoundError('Space member', row.id);\n }\n await outbox.append([\n entitlementEvents.spaceMemberUpdated.make({\n orgId,\n data: next,\n metadata: { actorOrgUserId: actor.id, previousStatus: row.status },\n }),\n ]);\n return { updated: next, previousStatus: row.status };\n },\n );\n this.logger.info('space member removed', {\n orgId,\n spaceId,\n orgUserId,\n previousStatus,\n actorId: actor.id,\n });\n return updated;\n }\n\n async listSpaceMembers(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n query: { page: number; pageSize: number },\n ): Promise<Page<SpaceMember>> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n if (!(await this.canOpen(orgId, actor, space))) {\n throw new NotFoundError('Space', spaceId);\n }\n return this.repo.listSpaceMembers(orgId, spaceId, {\n ...query,\n ...(canModerate(actor.role) ? {} : { statuses: ['active'] }),\n });\n }\n\n hasDueNotices(orgId: string, now: Date): Promise<boolean> {\n return this.repo.hasDueNotices(orgId, now, new Date(now.getTime() + ENDING_SOON_MS));\n }\n\n async expireDue(orgId: string, now: Date): Promise<void> {\n const until = new Date(now.getTime() + ENDING_SOON_MS);\n let appended = 0;\n let afterId: string | undefined;\n for (;;) {\n const batch = await this.uow.run(async ({ entitlements, outbox }) => {\n const events: NewDomainEvent[] = [];\n const grants = await entitlements.listGrantsExpiredBy(\n orgId,\n now,\n NOTICE_BATCH_SIZE,\n afterId,\n );\n for (const grant of grants) {\n if (!grant.expiresAt) {\n continue;\n }\n const claimed = await entitlements.claimNotice(\n orgId,\n { kind: 'expired', entitlementId: grant.id, expiresAt: grant.expiresAt },\n now,\n );\n if (claimed) {\n events.push(entitlementEvents.entitlementExpired.make({ orgId, data: grant }));\n }\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n return { events: events.length, last: grants.at(-1)?.id, size: grants.length };\n });\n appended += batch.events;\n if (batch.size < NOTICE_BATCH_SIZE || batch.last === undefined) {\n break;\n }\n afterId = batch.last;\n }\n let afterKey: EndingGrantKey | undefined;\n for (;;) {\n const batch = await this.uow.run(async ({ entitlements, outbox }) => {\n const events: NewDomainEvent[] = [];\n const ending = await entitlements.listGrantsEndingBetween(\n orgId,\n now,\n until,\n NOTICE_BATCH_SIZE,\n afterKey,\n );\n for (const { grant, communityId } of ending) {\n if (!grant.expiresAt) {\n continue;\n }\n const claimed = await entitlements.claimNotice(\n orgId,\n {\n kind: 'ending_soon',\n entitlementId: grant.id,\n communityId,\n expiresAt: grant.expiresAt,\n },\n now,\n );\n if (claimed) {\n events.push(\n entitlementEvents.entitlementEndingSoon.make({\n orgId,\n data: { ...grant, communityId },\n }),\n );\n }\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n const last = ending.at(-1);\n return {\n events: events.length,\n last: last && { entitlementId: last.grant.id, communityId: last.communityId },\n size: ending.length,\n };\n });\n appended += batch.events;\n if (batch.size < NOTICE_BATCH_SIZE || batch.last === undefined) {\n break;\n }\n afterKey = batch.last;\n }\n if (appended > 0) {\n this.logger.info('due grants expired', { orgId, events: appended });\n }\n }\n\n private async requireSpaceIn(orgId: string, communityId: string, spaceId: string) {\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space || space.communityId !== communityId) {\n this.logger.warn('space members rejected — space not found', {\n orgId,\n communityId,\n spaceId,\n });\n throw new NotFoundError('Space', spaceId);\n }\n return space;\n }\n\n private async requireVisibleSpaceIn(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ) {\n const space = await this.requireSpaceIn(orgId, communityId, spaceId);\n if (seesSpace(actor.role, space.accessRule, undefined)) {\n return space;\n }\n const row = await this.repo.findSpaceMember(orgId, spaceId, actor.id);\n if (!seesSpace(actor.role, space.accessRule, row?.status)) {\n throw new NotFoundError('Space', spaceId);\n }\n return space;\n }\n\n private assertHasMembers(accessRule: SpaceAccessRule): void {\n if (accessRule === 'all_members') {\n throw new ConflictError('An all-members space has no space members');\n }\n }\n\n private assertRule(accessRule: SpaceAccessRule, expected: 'by_request' | 'by_invite'): void {\n this.assertHasMembers(accessRule);\n if (accessRule !== expected) {\n throw new ConflictError(\n accessRule === 'by_invite' ? 'This space is by invitation' : 'This space is by request',\n );\n }\n }\n\n private assertHolder(actor: Actor): void {\n if (isStaffRole(actor.role)) {\n throw new ForbiddenError('Staff do not hold a place in a space');\n }\n }\n\n private async assertGrant(\n orgId: string,\n actor: Actor,\n orgUserId: string,\n communityId: string,\n ): Promise<void> {\n const member = await this.repo.hasCommunityAccess(orgId, orgUserId, communityId);\n if (!member) {\n throw new ForbiddenError(\n orgUserId === actor.id\n ? 'You are not a member of this community'\n : 'That person is not a member of this community',\n );\n }\n }\n\n private async assertHolds(\n orgId: string,\n actor: Actor,\n orgUserId: string,\n communityId: string,\n ): Promise<void> {\n const facts = await this.accessOf(orgId, orgUserId, communityId, null);\n if (facts && isStaffRole(facts.role)) {\n throw new ForbiddenError('Staff do not hold a place in a space');\n }\n await this.assertGrant(orgId, actor, orgUserId, communityId);\n }\n\n private async assertCanManage(orgId: string, actor: Actor, communityId: string): Promise<void> {\n const facts = await this.accessOf(orgId, actor.id, communityId, null);\n if (facts?.member && canModerate(facts.role)) {\n return;\n }\n throw new ForbiddenError('You cannot manage members of this space');\n }\n\n private async accessOf(\n orgId: string,\n orgUserId: string,\n communityId: string,\n spaceId: string | null,\n ): Promise<AccessFacts | null> {\n const [facts] = await this.repo.accessAmong(orgId, communityId, spaceId, [orgUserId]);\n return facts ?? null;\n }\n\n private async createMember(\n orgId: string,\n actor: Actor,\n space: { communityId: string; id: string },\n orgUserId: string,\n status: InsertSpaceMemberInput['status'],\n source: InsertSpaceMemberInput['source'],\n ): Promise<SpaceMember> {\n return this.uow.run(async ({ entitlements, outbox }) =>\n this.insertMember(entitlements, outbox, orgId, actor, {\n communityId: space.communityId,\n spaceId: space.id,\n orgUserId,\n status,\n source,\n }),\n );\n }\n\n private async insertMember(\n entitlements: EntitlementsRepository,\n outbox: OutboxAppender,\n orgId: string,\n actor: Actor,\n input: InsertSpaceMemberInput,\n room?: Space,\n ): Promise<SpaceMember> {\n const existing = await entitlements.lockSpaceMember(orgId, input.spaceId, input.orgUserId);\n if (existing && existing.status !== 'revoked') {\n throw new ConflictError(\n input.orgUserId === actor.id\n ? 'You already have a place in this space'\n : 'That person already has a place in this space',\n );\n }\n if (room) {\n await this.assertRoom(entitlements, orgId, room);\n }\n if (existing) {\n await entitlements.deleteSpaceMember(orgId, existing.id);\n }\n const created = await entitlements.insertSpaceMember(orgId, input);\n await outbox.append([\n entitlementEvents.spaceMemberCreated.make({\n orgId,\n data: created,\n metadata: { actorOrgUserId: actor.id },\n }),\n ]);\n return created;\n }\n\n private async activate(\n orgId: string,\n actor: Actor,\n spaceId: string,\n orgUserId: string,\n rule: 'by_request' | 'by_invite',\n from: SpaceMember['status'],\n missing: () => Error,\n ): Promise<SpaceMember> {\n const updated = await this.uow.run(async ({ entitlements, spaces, outbox }) => {\n const locked = await this.lockSpace(spaces, orgId, spaceId);\n this.assertRule(locked.accessRule, rule);\n const row = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (row?.status === 'active') {\n throw alreadyPlaced(actor, orgUserId);\n }\n if (!row || row.status !== from) {\n this.logger.warn('space member activate rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw missing();\n }\n await this.assertRoom(entitlements, orgId, locked);\n const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, 'active');\n if (!next) {\n throw missing();\n }\n await outbox.append([\n entitlementEvents.spaceMemberUpdated.make({\n orgId,\n data: next,\n metadata: { actorOrgUserId: actor.id, previousStatus: from },\n }),\n ]);\n return next;\n });\n this.logger.info('space member activated', { orgId, spaceId, orgUserId, actorId: actor.id });\n return updated;\n }\n\n private async deleteMember(\n orgId: string,\n actor: Actor,\n spaceId: string,\n orgUserId: string,\n from: SpaceMember['status'],\n missing: () => Error,\n ): Promise<SpaceMember> {\n const row = await this.uow.run(async ({ entitlements, outbox }) => {\n const current = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (from !== 'active' && current?.status === 'active') {\n throw alreadyPlaced(actor, orgUserId);\n }\n if (!current || current.status !== from) {\n this.logger.warn('space member delete rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw missing();\n }\n await entitlements.deleteSpaceMember(orgId, current.id);\n await outbox.append([\n entitlementEvents.spaceMemberDeleted.make({\n orgId,\n data: current,\n metadata: { actorOrgUserId: actor.id, previousStatus: from },\n }),\n ]);\n return current;\n });\n this.logger.info('space member deleted', {\n orgId,\n spaceId,\n orgUserId,\n previousStatus: from,\n actorId: actor.id,\n });\n return row;\n }\n\n private async lockSpace(spaces: SpaceRowLock, orgId: string, spaceId: string) {\n const locked = await spaces.lockSpace(orgId, spaceId);\n if (!locked) {\n throw new NotFoundError('Space', spaceId);\n }\n return locked;\n }\n\n private async assertRoom(\n entitlements: EntitlementsRepository,\n orgId: string,\n space: Space,\n ): Promise<void> {\n if (space.capacity === null) {\n return;\n }\n const count = await entitlements.countActiveSpaceMembers(orgId, space.id);\n if (count >= space.capacity) {\n throw new ConflictError('This space is full');\n }\n }\n}\n\nfunction alreadyPlaced(actor: Actor, orgUserId: string): ConflictError {\n return new ConflictError(\n orgUserId === actor.id\n ? 'You already have a place in this space'\n : 'That person already has a place in this space',\n );\n}\n\nfunction ownRowMissing(status: 'invited' | 'active'): ConflictError {\n return new ConflictError(\n status === 'invited'\n ? 'You do not have an invitation to this space'\n : 'You are not a member of this space',\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service.js","names":[],"sources":["../../src/entitlements/service.ts"],"sourcesContent":["// entitlements context — service implementation (inbound port).\n//\n// Mutations run inside the context's UnitOfWork: the domain write and the\n// outbox append commit in ONE transaction (transactional outbox). This\n// service never publishes — the outbox relay republishes committed events to\n// the EventBus at-least-once.\nimport type { Event, Space, SpaceRowLock } from '../content/index.js';\nimport type {\n ContentType,\n Entitlement,\n EntitlementsQuery,\n GrantEntitlementInput,\n GrantSource,\n Page,\n SpaceMember,\n} from './model.js';\nimport type {\n AccessFacts,\n EntitlementsRepository,\n EntitlementsService,\n EntitlementsUnitOfWork,\n EndingGrantKey,\n GrantTarget,\n InsertSpaceMemberInput,\n OpenableSpace,\n PermissionHolders,\n RevokeGrantInput,\n SpaceAccessRule,\n ContentReader,\n} from './ports.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { Logger, NewDomainEvent, OutboxAppender } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { ConflictError, ForbiddenError, NotFoundError } from '../shared/errors.js';\nimport { entitlementEvents } from './events.js';\nimport { opensSpace, seesSpace } from './rules.js';\n\nconst ENDING_SOON_MS = 7 * 24 * 60 * 60 * 1000;\nconst NOTICE_BATCH_SIZE = 100;\n\ninterface CommunityTeam {\n readers: Set<string>;\n moderators: Set<string>;\n}\n\ninterface Standing {\n member: boolean;\n everySpace: boolean;\n activeInSpace: boolean;\n}\n\nexport type EntitlementsServiceParams = {\n /** Read-only access (list) — runs outside any transaction. */\n repo: EntitlementsRepository;\n content: ContentReader;\n permissions: PermissionHolders;\n /** Atomic write scope: tx-bound repo + outbox appender. */\n uow: EntitlementsUnitOfWork;\n logger?: Logger;\n};\n\nexport class EntitlementsServiceImpl implements EntitlementsService {\n private readonly repo: EntitlementsRepository;\n private readonly content: ContentReader;\n private readonly permissions: PermissionHolders;\n private readonly uow: EntitlementsUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: EntitlementsServiceParams) {\n this.repo = params.repo;\n this.content = params.content;\n this.permissions = params.permissions;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n async list(query: EntitlementsQuery, actor: Actor): Promise<Page<Entitlement>> {\n const orgId = actor.orgId;\n if (!actor.permissions.has('access:read')) {\n const contentId = query.contentId;\n const learners =\n contentId !== undefined &&\n actor.permissions.has('progress:read', contentId) &&\n (await this.repo.contentTypeOf(orgId, contentId)) === 'course';\n if (!learners) {\n this.logger.warn('entitlements list refused', {\n orgId,\n actorId: actor.id,\n contentId: contentId ?? null,\n });\n throw new ForbiddenError(\n contentId === undefined\n ? 'You need access:read'\n : 'You need access:read, or progress:read on this course, to see who holds it',\n );\n }\n }\n return this.repo.list(orgId, query);\n }\n\n async grant(input: GrantEntitlementInput, actor: Actor): Promise<Entitlement> {\n const orgId = actor.orgId;\n const { contentType, ...grant } = input;\n const contentId = grant.contentId ?? null;\n const type = contentId === null ? null : await this.repo.contentTypeOf(orgId, contentId);\n if (contentType !== undefined && type !== contentType) {\n throw new NotFoundError(contentType === 'event' ? 'Event' : 'Content', contentId ?? '');\n }\n if (contentId !== null && type === 'event') {\n return this.grantEvent({ ...grant, contentId }, actor);\n }\n actor.permissions.require('access:write');\n if (contentId !== null && type === null) {\n throw new NotFoundError('Content', contentId);\n }\n const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {\n const created = await entitlements.insert(orgId, {\n ...grant,\n source: this.sourceOf(actor, grant.orgUserId),\n });\n await outbox.append([\n entitlementEvents.entitlementCreated.make({\n orgId,\n data: created,\n metadata: this.actorMetadata(actor),\n }),\n ]);\n return created;\n });\n this.logger.info('entitlement granted', {\n orgId,\n entitlementId: entitlement.id,\n orgUserId: entitlement.orgUserId,\n bundleId: entitlement.bundleId,\n contentId: entitlement.contentId,\n source: entitlement.source,\n actorId: actor.id,\n });\n return entitlement;\n }\n\n async revoke(input: RevokeGrantInput, actor: Actor): Promise<Entitlement> {\n const orgId = actor.orgId;\n const { orgUserId } = input;\n const contentId = input.contentId ?? null;\n const bundleId = input.bundleId ?? null;\n const type = contentId === null ? null : await this.repo.contentTypeOf(orgId, contentId);\n if (input.contentType !== undefined && type !== input.contentType) {\n throw new NotFoundError(input.contentType === 'event' ? 'Event' : 'Content', contentId ?? '');\n }\n if (type === 'event' && contentId !== null) {\n if (orgUserId !== actor.id) {\n this.requireAttendeeManager(actor, contentId);\n }\n } else {\n actor.permissions.require('access:write');\n if (contentId !== null && type === null) {\n throw new NotFoundError('Content', contentId);\n }\n }\n const target = contentId ?? bundleId;\n const revoked = await this.uow.run(async ({ entitlements, outbox }) => {\n const existing = await entitlements.lockGrant(orgId, { orgUserId, contentId, bundleId });\n if (!existing || !activeNow(existing, new Date())) {\n this.logger.warn('entitlement revoke rejected — no direct active grant', {\n orgId,\n orgUserId,\n contentId,\n bundleId,\n actorId: actor.id,\n });\n throw new NotFoundError('Entitlement', `${orgUserId}:${target}`);\n }\n const updated = await entitlements.setStatus(orgId, existing.id, 'revoked');\n if (!updated) {\n throw new NotFoundError('Entitlement', existing.id);\n }\n await outbox.append([\n entitlementEvents.entitlementDeleted.make({\n orgId,\n data: updated,\n metadata: this.actorMetadata(actor),\n }),\n ]);\n return updated;\n });\n this.logger.info('entitlement revoked', {\n orgId,\n entitlementId: revoked.id,\n orgUserId,\n contentId,\n bundleId,\n actorId: actor.id,\n });\n return revoked;\n }\n\n private async grantEvent(\n input: GrantEntitlementInput & { contentId: string },\n actor: Actor,\n ): Promise<Entitlement> {\n const orgId = actor.orgId;\n const eventId = input.contentId;\n const orgUserId = input.orgUserId;\n const self = orgUserId === actor.id;\n if (!self) {\n this.requireAttendeeManager(actor, eventId);\n }\n const event = await this.content.getEvent(eventId, this.system(orgId));\n const now = new Date();\n if (self) {\n await this.assertCanRegister(event, actor, now);\n } else {\n assertOpenForAttendees(event, now);\n }\n const { entitlement, created } = await this.uow.run(async ({ entitlements, outbox }) => {\n if (event.settings.capacity !== null) {\n await entitlements.lockCapacity(orgId, eventId);\n }\n const existing = await entitlements.lockGrant(orgId, { orgUserId, contentId: eventId });\n if (existing && activeNow(existing, now)) {\n if (self) {\n throw alreadyRegistered();\n }\n return { entitlement: existing, created: false };\n }\n await this.assertPlace(entitlements, orgId, event, orgUserId);\n const inserted = await entitlements.insert(orgId, {\n orgUserId,\n contentId: eventId,\n expiresAt: self ? null : (input.expiresAt ?? null),\n source: this.sourceOf(actor, orgUserId),\n });\n await outbox.append([\n entitlementEvents.entitlementCreated.make({\n orgId,\n data: inserted,\n metadata: this.actorMetadata(actor),\n }),\n ]);\n return { entitlement: inserted, created: true };\n });\n if (created) {\n this.logger.info('entitlement granted', {\n orgId,\n entitlementId: entitlement.id,\n orgUserId,\n contentId: eventId,\n source: entitlement.source,\n actorId: actor.id,\n });\n }\n return entitlement;\n }\n\n private async assertCanRegister(event: Event, actor: Actor, now: Date): Promise<void> {\n if (event.status !== 'published') {\n throw new NotFoundError('Event', event.id);\n }\n if (await this.repo.hasAccess(actor.orgId, actor.id, event.id)) {\n throw alreadyRegistered();\n }\n if (event.settings.type !== 'public') {\n this.logger.warn('event registration refused — private', {\n orgId: actor.orgId,\n eventId: event.id,\n actorId: actor.id,\n });\n throw new ForbiddenError('This event is private: only its organizers can add attendees');\n }\n if (event.startsAt.getTime() <= now.getTime()) {\n throw new ConflictError('This event has already started', 'started');\n }\n }\n\n private requireAttendeeManager(actor: Actor, eventId: string): void {\n if (actor.permissions.has('access:write') || actor.permissions.has('events:write', eventId)) {\n return;\n }\n this.logger.warn('event attendees refused — cannot manage', {\n orgId: actor.orgId,\n eventId,\n actorId: actor.id,\n });\n throw new ForbiddenError('You need access:write, or events:write on this event');\n }\n\n private async assertPlace(\n entitlements: EntitlementsRepository,\n orgId: string,\n event: Event,\n orgUserId: string,\n ): Promise<void> {\n const capacity = event.settings.capacity;\n if (capacity === null) {\n return;\n }\n const attendees = await entitlements.grantedOrgUserIds(orgId, event.id);\n if (attendees.filter((id) => id !== orgUserId).length >= capacity) {\n throw new ConflictError('This event is full', 'full');\n }\n }\n\n private sourceOf(actor: Actor, orgUserId: string): GrantSource {\n if (actor.id === this.system(actor.orgId).id) {\n return 'automation';\n }\n return orgUserId === actor.id ? 'self' : 'manual';\n }\n\n private actorMetadata(actor: Actor): { actorId?: string } {\n return actor.id === this.system(actor.orgId).id ? {} : { actorId: actor.id };\n }\n\n hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean> {\n return this.repo.hasAccess(orgId, orgUserId, contentId);\n }\n\n listAccessibleContent(orgId: string, orgUserId: string, type?: ContentType): Promise<string[]> {\n return this.repo.listAccessibleContent(orgId, orgUserId, type);\n }\n\n holdsAnyGrant(orgId: string, orgUserId: string): Promise<boolean> {\n return this.repo.holdsAnyGrant(orgId, orgUserId);\n }\n\n async opensEverySpace(actor: Actor, communityId: string): Promise<boolean> {\n return (await this.standingOf(actor, communityId, null)).everySpace;\n }\n\n async isCommunityMember(communityId: string, actor: Actor): Promise<boolean> {\n if (actor.permissions.has('communities:read', communityId)) {\n return true;\n }\n return this.repo.hasCommunityAccess(actor.orgId, actor.id, communityId);\n }\n\n async canOpenSpace(spaceId: string, actor: Actor): Promise<boolean> {\n const space = await this.content.getSpace(spaceId, this.system(actor.orgId));\n if (!space) {\n return false;\n }\n return this.canOpen(space, actor);\n }\n\n async canOpen(space: OpenableSpace, actor: Actor): Promise<boolean> {\n const standing = await this.standingOf(actor, space.communityId, space.id);\n return opensSpace(\n standing.member,\n standing.everySpace,\n space.accessRule,\n standing.activeInSpace,\n );\n }\n\n async spaceStandings(\n communityId: string,\n actor: Actor,\n ): Promise<Record<string, SpaceMember['status']>> {\n if ((await this.standingOf(actor, communityId, null)).everySpace) {\n return {};\n }\n const rows = await this.repo.listSpaceMembersFor(actor.orgId, actor.id, communityId);\n return Object.fromEntries(rows.map((row) => [row.spaceId, row.status]));\n }\n\n async openSpacesFor(communityId: string, actor: Actor): Promise<Space[]> {\n const orgId = actor.orgId;\n const standing = await this.standingOf(actor, communityId, null);\n if (!standing.member) {\n return [];\n }\n const [spaces, activeIds] = await Promise.all([\n this.content.listSpaces(communityId, this.system(orgId)),\n standing.everySpace\n ? Promise.resolve([])\n : this.repo.activeSpaceIds(orgId, actor.id, communityId),\n ]);\n const active = new Set(activeIds);\n return spaces.filter((space) =>\n opensSpace(true, standing.everySpace, space.accessRule, active.has(space.id)),\n );\n }\n\n async openSpaceIds(communityId: string, actor: Actor): Promise<string[]> {\n const spaces = await this.openSpacesFor(communityId, actor);\n return spaces.map((space) => space.id);\n }\n\n async communityMembersAmong(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<string[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n const [facts, team] = await Promise.all([\n this.repo.accessAmong(orgId, communityId, null, orgUserIds),\n this.teamOf(orgId, communityId),\n ]);\n return facts.filter((fact) => standingFrom(fact, team).member).map((fact) => fact.orgUserId);\n }\n\n async spaceOpenersAmong(orgId: string, spaceId: string, orgUserIds: string[]): Promise<string[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n const space = await this.content.getSpace(spaceId, this.system(orgId));\n if (!space) {\n return [];\n }\n const [facts, team] = await Promise.all([\n this.repo.accessAmong(orgId, space.communityId, space.id, orgUserIds),\n this.teamOf(orgId, space.communityId),\n ]);\n return facts\n .filter((fact) => {\n const standing = standingFrom(fact, team);\n return opensSpace(\n standing.member,\n standing.everySpace,\n space.accessRule,\n standing.activeInSpace,\n );\n })\n .map((fact) => fact.orgUserId);\n }\n\n async spaceOpeners(orgId: string, spaceId: string): Promise<string[]> {\n const space = await this.content.getSpace(spaceId, this.system(orgId));\n if (!space) {\n return [];\n }\n const members = await this.communityMemberIds(orgId, space.communityId);\n return this.spaceOpenersAmong(orgId, spaceId, members);\n }\n\n communitiesOpenedBy(orgId: string, grant: GrantTarget): Promise<string[]> {\n return this.repo.communitiesOpenedBy(orgId, grant);\n }\n\n async communityModeratorIds(orgId: string, communityId: string): Promise<string[]> {\n const moderators = await this.permissions.holders(orgId, 'communities:moderate', communityId);\n return this.communityMembersAmong(orgId, communityId, moderators);\n }\n\n communityIdsOpenedFor(orgId: string, orgUserId: string): Promise<string[]> {\n return this.repo.communityIdsOpenedFor(orgId, orgUserId);\n }\n\n openCommunityIds(actor: Actor): Promise<string[]> {\n const readable = actor.permissions.where('communities:read');\n return this.repo.openCommunityIds(actor.orgId, actor.id, {\n all: readable.org,\n ids: readable.content,\n });\n }\n\n async communityMemberIds(orgId: string, communityId: string): Promise<string[]> {\n const readers = await this.permissions.holders(orgId, 'communities:read', communityId);\n return this.repo.communityMemberIds(orgId, communityId, readers);\n }\n\n grantedOrgUserIds(orgId: string, contentId: string): Promise<string[]> {\n return this.repo.grantedOrgUserIds(orgId, contentId);\n }\n\n async countActiveSpaceMembers(orgId: string, spaceId: string): Promise<number> {\n const space = await this.content.getSpace(spaceId, this.system(orgId));\n if (!space) {\n return 0;\n }\n const excluding = await this.everySpaceOpeners(orgId, space.communityId);\n return this.repo.countActiveSpaceMembers(orgId, spaceId, excluding);\n }\n\n async everySpaceOpeners(orgId: string, communityId: string): Promise<string[]> {\n const team = await this.teamOf(orgId, communityId);\n const moderators = [...team.moderators].filter((id) => !team.readers.has(id));\n const granted =\n moderators.length === 0\n ? []\n : (await this.repo.accessAmong(orgId, communityId, null, moderators))\n .filter((fact) => fact.granted)\n .map((fact) => fact.orgUserId);\n return [...team.readers, ...granted];\n }\n\n async requestJoin(communityId: string, spaceId: string, actor: Actor): Promise<SpaceMember> {\n const orgId = actor.orgId;\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_request');\n await this.assertHolder(actor, space.communityId);\n await this.assertMember(actor, actor.id, space.communityId);\n const created = await this.createMember(orgId, actor, space, actor.id, 'requested', 'request');\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId: actor.id,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async invite(\n communityId: string,\n spaceId: string,\n orgUserId: string,\n actor: Actor,\n ): Promise<SpaceMember> {\n const orgId = actor.orgId;\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n await this.assertCanManage(actor, space.communityId);\n this.assertRule(space.accessRule, 'by_invite');\n await this.assertHolds(actor, orgUserId, space.communityId);\n const created = await this.createMember(orgId, actor, space, orgUserId, 'invited', 'invite');\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async addSpaceMember(\n communityId: string,\n spaceId: string,\n orgUserId: string,\n actor: Actor,\n ): Promise<SpaceMember> {\n const orgId = actor.orgId;\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n await this.assertCanManage(actor, space.communityId);\n this.assertHasMembers(space.accessRule);\n await this.assertHolds(actor, orgUserId, space.communityId);\n const excluding = await this.everySpaceOpeners(orgId, space.communityId);\n const created = await this.uow.run(async (scope) => {\n const locked = await this.lockSpace(scope.spaces, orgId, spaceId);\n this.assertHasMembers(locked.accessRule);\n return this.insertMember(\n scope.entitlements,\n scope.outbox,\n orgId,\n actor,\n {\n communityId: space.communityId,\n spaceId,\n orgUserId,\n status: 'active',\n source: 'manual',\n },\n { space: locked, excluding },\n );\n });\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async accept(communityId: string, spaceId: string, actor: Actor): Promise<SpaceMember> {\n const orgId = actor.orgId;\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_invite');\n await this.assertHolder(actor, space.communityId);\n await this.assertMember(actor, actor.id, space.communityId);\n const excluding = await this.everySpaceOpeners(orgId, space.communityId);\n return this.activate(orgId, actor, spaceId, actor.id, 'by_invite', 'invited', excluding, () =>\n ownRowMissing('invited'),\n );\n }\n\n async approve(\n communityId: string,\n spaceId: string,\n orgUserId: string,\n actor: Actor,\n ): Promise<SpaceMember> {\n const orgId = actor.orgId;\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n await this.assertCanManage(actor, space.communityId);\n this.assertRule(space.accessRule, 'by_request');\n await this.assertHolds(actor, orgUserId, space.communityId);\n const excluding = await this.everySpaceOpeners(orgId, space.communityId);\n return this.activate(\n orgId,\n actor,\n spaceId,\n orgUserId,\n 'by_request',\n 'requested',\n excluding,\n () => new NotFoundError('Space member', orgUserId),\n );\n }\n\n async decline(\n communityId: string,\n spaceId: string,\n orgUserId: string,\n actor: Actor,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n await this.assertCanManage(actor, space.communityId);\n return this.deleteMember(\n actor.orgId,\n actor,\n spaceId,\n orgUserId,\n 'requested',\n () => new NotFoundError('Space member', orgUserId),\n );\n }\n\n async refuse(communityId: string, spaceId: string, actor: Actor): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n await this.assertHolder(actor, space.communityId);\n await this.assertMember(actor, actor.id, space.communityId);\n return this.deleteMember(actor.orgId, actor, spaceId, actor.id, 'invited', () =>\n ownRowMissing('invited'),\n );\n }\n\n async leave(communityId: string, spaceId: string, actor: Actor): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n await this.assertHolder(actor, space.communityId);\n await this.assertMember(actor, actor.id, space.communityId);\n return this.deleteMember(actor.orgId, actor, spaceId, actor.id, 'active', () =>\n ownRowMissing('active'),\n );\n }\n\n async remove(\n communityId: string,\n spaceId: string,\n orgUserId: string,\n actor: Actor,\n ): Promise<SpaceMember> {\n const orgId = actor.orgId;\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n await this.assertCanManage(actor, space.communityId);\n this.assertHasMembers(space.accessRule);\n const { updated, previousStatus } = await this.uow.run(\n async ({ entitlements, spaces, outbox }) => {\n const locked = await this.lockSpace(spaces, orgId, spaceId);\n this.assertHasMembers(locked.accessRule);\n const row = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (!row) {\n this.logger.warn('space member remove rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw new NotFoundError('Space member', orgUserId);\n }\n if (row.status === 'revoked') {\n throw new ConflictError('This person is already removed from the space');\n }\n const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, 'revoked');\n if (!next) {\n throw new NotFoundError('Space member', row.id);\n }\n await outbox.append([\n entitlementEvents.spaceMemberUpdated.make({\n orgId,\n data: next,\n metadata: { actorId: actor.id, previousStatus: row.status },\n }),\n ]);\n return { updated: next, previousStatus: row.status };\n },\n );\n this.logger.info('space member removed', {\n orgId,\n spaceId,\n orgUserId,\n previousStatus,\n actorId: actor.id,\n });\n return updated;\n }\n\n async listSpaceMembers(\n communityId: string,\n spaceId: string,\n query: { page: number; pageSize: number },\n actor: Actor,\n ): Promise<Page<SpaceMember>> {\n const orgId = actor.orgId;\n const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);\n if (!(await this.canOpen(space, actor))) {\n throw new NotFoundError('Space', spaceId);\n }\n const excluding = await this.everySpaceOpeners(orgId, space.communityId);\n return this.repo.listSpaceMembers(orgId, spaceId, {\n ...query,\n excluding,\n ...(actor.permissions.has('communities:moderate', space.communityId)\n ? {}\n : { statuses: ['active'] }),\n });\n }\n\n async expireDueGrants(now: Date): Promise<void> {\n const orgIds = await this.repo.orgIdsWithDueNotices(\n now,\n new Date(now.getTime() + ENDING_SOON_MS),\n );\n let failed = 0;\n for (const orgId of orgIds) {\n try {\n await this.expireDueIn(orgId, now);\n } catch (err) {\n failed += 1;\n this.logger.error('due grants not expired', { orgId, err });\n }\n }\n if (failed > 0) {\n throw new Error(`due grants of ${failed} orgs could not be expired`);\n }\n }\n\n private async expireDueIn(orgId: string, now: Date): Promise<void> {\n const until = new Date(now.getTime() + ENDING_SOON_MS);\n let appended = 0;\n let afterId: string | undefined;\n for (;;) {\n const batch = await this.uow.run(async ({ entitlements, outbox }) => {\n const events: NewDomainEvent[] = [];\n const grants = await entitlements.listGrantsExpiredBy(\n orgId,\n now,\n NOTICE_BATCH_SIZE,\n afterId,\n );\n for (const grant of grants) {\n if (!grant.expiresAt) {\n continue;\n }\n const claimed = await entitlements.claimNotice(\n orgId,\n { kind: 'expired', entitlementId: grant.id, expiresAt: grant.expiresAt },\n now,\n );\n if (claimed) {\n events.push(entitlementEvents.entitlementExpired.make({ orgId, data: grant }));\n }\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n return { events: events.length, last: grants.at(-1)?.id, size: grants.length };\n });\n appended += batch.events;\n if (batch.size < NOTICE_BATCH_SIZE || batch.last === undefined) {\n break;\n }\n afterId = batch.last;\n }\n let afterKey: EndingGrantKey | undefined;\n for (;;) {\n const batch = await this.uow.run(async ({ entitlements, outbox }) => {\n const events: NewDomainEvent[] = [];\n const ending = await entitlements.listGrantsEndingBetween(\n orgId,\n now,\n until,\n NOTICE_BATCH_SIZE,\n afterKey,\n );\n for (const { grant, communityId } of ending) {\n if (!grant.expiresAt) {\n continue;\n }\n const claimed = await entitlements.claimNotice(\n orgId,\n {\n kind: 'ending_soon',\n entitlementId: grant.id,\n communityId,\n expiresAt: grant.expiresAt,\n },\n now,\n );\n if (claimed) {\n events.push(\n entitlementEvents.entitlementEndingSoon.make({\n orgId,\n data: { ...grant, communityId },\n }),\n );\n }\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n const last = ending.at(-1);\n return {\n events: events.length,\n last: last && { entitlementId: last.grant.id, communityId: last.communityId },\n size: ending.length,\n };\n });\n appended += batch.events;\n if (batch.size < NOTICE_BATCH_SIZE || batch.last === undefined) {\n break;\n }\n afterKey = batch.last;\n }\n if (appended > 0) {\n this.logger.info('due grants expired', { orgId, events: appended });\n }\n }\n\n private system(orgId: string): Actor {\n return this.permissions.systemActor(orgId);\n }\n\n private async teamOf(orgId: string, communityId: string): Promise<CommunityTeam> {\n const [readers, moderators] = await Promise.all([\n this.permissions.holders(orgId, 'communities:read', communityId),\n this.permissions.holders(orgId, 'communities:moderate', communityId),\n ]);\n return { readers: new Set(readers), moderators: new Set(moderators) };\n }\n\n private async standingOf(\n actor: Actor,\n communityId: string,\n spaceId: string | null,\n ): Promise<Standing> {\n const reads = actor.permissions.has('communities:read', communityId);\n const moderates = actor.permissions.has('communities:moderate', communityId);\n if (reads) {\n return { member: true, everySpace: true, activeInSpace: false };\n }\n const facts = await this.accessOf(actor.orgId, actor.id, communityId, spaceId);\n const member = facts?.granted === true;\n return {\n member,\n everySpace: member && moderates,\n activeInSpace: facts?.activeInSpace === true,\n };\n }\n\n private async requireSpaceIn(orgId: string, communityId: string, spaceId: string) {\n const space = await this.content.getSpace(spaceId, this.system(orgId));\n if (!space || space.communityId !== communityId) {\n this.logger.warn('space members rejected — space not found', {\n orgId,\n communityId,\n spaceId,\n });\n throw new NotFoundError('Space', spaceId);\n }\n return space;\n }\n\n private async requireVisibleSpaceIn(actor: Actor, communityId: string, spaceId: string) {\n const orgId = actor.orgId;\n const space = await this.requireSpaceIn(orgId, communityId, spaceId);\n if (seesSpace(false, space.accessRule, undefined)) {\n return space;\n }\n if (await this.opensEverySpace(actor, communityId)) {\n return space;\n }\n const row = await this.repo.findSpaceMember(orgId, spaceId, actor.id);\n if (!seesSpace(false, space.accessRule, row?.status)) {\n throw new NotFoundError('Space', spaceId);\n }\n return space;\n }\n\n private assertHasMembers(accessRule: SpaceAccessRule): void {\n if (accessRule === 'all_members') {\n throw new ConflictError('An all-members space has no space members');\n }\n }\n\n private assertRule(accessRule: SpaceAccessRule, expected: 'by_request' | 'by_invite'): void {\n this.assertHasMembers(accessRule);\n if (accessRule !== expected) {\n throw new ConflictError(\n accessRule === 'by_invite' ? 'This space is by invitation' : 'This space is by request',\n );\n }\n }\n\n private async assertHolder(actor: Actor, communityId: string): Promise<void> {\n if (await this.opensEverySpace(actor, communityId)) {\n this.logger.warn('space place refused — opens every space', {\n orgId: actor.orgId,\n communityId,\n actorId: actor.id,\n });\n throw new ForbiddenError('You open every space in this community and hold no place in them');\n }\n }\n\n private async assertMember(actor: Actor, orgUserId: string, communityId: string): Promise<void> {\n const member =\n orgUserId === actor.id\n ? await this.isCommunityMember(communityId, actor)\n : (await this.communityMembersAmong(actor.orgId, communityId, [orgUserId])).length > 0;\n if (!member) {\n throw new ForbiddenError(\n orgUserId === actor.id\n ? 'You are not a member of this community'\n : 'That person is not a member of this community',\n );\n }\n }\n\n private async assertHolds(actor: Actor, orgUserId: string, communityId: string): Promise<void> {\n const openers = await this.everySpaceOpeners(actor.orgId, communityId);\n if (openers.includes(orgUserId)) {\n this.logger.warn('space place refused — person opens every space', {\n orgId: actor.orgId,\n communityId,\n orgUserId,\n actorId: actor.id,\n });\n throw new ForbiddenError(\n 'That person opens every space in this community and holds no place in them',\n );\n }\n await this.assertMember(actor, orgUserId, communityId);\n }\n\n private async assertCanManage(actor: Actor, communityId: string): Promise<void> {\n if (\n actor.permissions.has('communities:moderate', communityId) &&\n (await this.isCommunityMember(communityId, actor))\n ) {\n return;\n }\n this.logger.warn('space members refused — cannot manage', {\n orgId: actor.orgId,\n communityId,\n actorId: actor.id,\n });\n throw new ForbiddenError('You need communities:moderate on this community, as its member');\n }\n\n private async accessOf(\n orgId: string,\n orgUserId: string,\n communityId: string,\n spaceId: string | null,\n ): Promise<AccessFacts | null> {\n const [facts] = await this.repo.accessAmong(orgId, communityId, spaceId, [orgUserId]);\n return facts ?? null;\n }\n\n private async createMember(\n orgId: string,\n actor: Actor,\n space: { communityId: string; id: string },\n orgUserId: string,\n status: InsertSpaceMemberInput['status'],\n source: InsertSpaceMemberInput['source'],\n ): Promise<SpaceMember> {\n return this.uow.run(async ({ entitlements, outbox }) =>\n this.insertMember(entitlements, outbox, orgId, actor, {\n communityId: space.communityId,\n spaceId: space.id,\n orgUserId,\n status,\n source,\n }),\n );\n }\n\n private async insertMember(\n entitlements: EntitlementsRepository,\n outbox: OutboxAppender,\n orgId: string,\n actor: Actor,\n input: InsertSpaceMemberInput,\n room?: { space: Space; excluding: string[] },\n ): Promise<SpaceMember> {\n const existing = await entitlements.lockSpaceMember(orgId, input.spaceId, input.orgUserId);\n if (existing && existing.status !== 'revoked') {\n throw new ConflictError(\n input.orgUserId === actor.id\n ? 'You already have a place in this space'\n : 'That person already has a place in this space',\n );\n }\n if (room) {\n await this.assertRoom(entitlements, orgId, room.space, room.excluding);\n }\n if (existing) {\n await entitlements.deleteSpaceMember(orgId, existing.id);\n }\n const created = await entitlements.insertSpaceMember(orgId, input);\n await outbox.append([\n entitlementEvents.spaceMemberCreated.make({\n orgId,\n data: created,\n metadata: { actorId: actor.id },\n }),\n ]);\n return created;\n }\n\n private async activate(\n orgId: string,\n actor: Actor,\n spaceId: string,\n orgUserId: string,\n rule: 'by_request' | 'by_invite',\n from: SpaceMember['status'],\n excluding: string[],\n missing: () => Error,\n ): Promise<SpaceMember> {\n const updated = await this.uow.run(async ({ entitlements, spaces, outbox }) => {\n const locked = await this.lockSpace(spaces, orgId, spaceId);\n this.assertRule(locked.accessRule, rule);\n const row = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (row?.status === 'active') {\n throw alreadyPlaced(actor, orgUserId);\n }\n if (!row || row.status !== from) {\n this.logger.warn('space member activate rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw missing();\n }\n await this.assertRoom(entitlements, orgId, locked, excluding);\n const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, 'active');\n if (!next) {\n throw missing();\n }\n await outbox.append([\n entitlementEvents.spaceMemberUpdated.make({\n orgId,\n data: next,\n metadata: { actorId: actor.id, previousStatus: from },\n }),\n ]);\n return next;\n });\n this.logger.info('space member activated', { orgId, spaceId, orgUserId, actorId: actor.id });\n return updated;\n }\n\n private async deleteMember(\n orgId: string,\n actor: Actor,\n spaceId: string,\n orgUserId: string,\n from: SpaceMember['status'],\n missing: () => Error,\n ): Promise<SpaceMember> {\n const row = await this.uow.run(async ({ entitlements, outbox }) => {\n const current = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (from !== 'active' && current?.status === 'active') {\n throw alreadyPlaced(actor, orgUserId);\n }\n if (!current || current.status !== from) {\n this.logger.warn('space member delete rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw missing();\n }\n await entitlements.deleteSpaceMember(orgId, current.id);\n await outbox.append([\n entitlementEvents.spaceMemberDeleted.make({\n orgId,\n data: current,\n metadata: { actorId: actor.id, previousStatus: from },\n }),\n ]);\n return current;\n });\n this.logger.info('space member deleted', {\n orgId,\n spaceId,\n orgUserId,\n previousStatus: from,\n actorId: actor.id,\n });\n return row;\n }\n\n private async lockSpace(spaces: SpaceRowLock, orgId: string, spaceId: string) {\n const locked = await spaces.lockSpace(orgId, spaceId);\n if (!locked) {\n throw new NotFoundError('Space', spaceId);\n }\n return locked;\n }\n\n private async assertRoom(\n entitlements: EntitlementsRepository,\n orgId: string,\n space: Space,\n excluding: string[],\n ): Promise<void> {\n if (space.capacity === null) {\n return;\n }\n const count = await entitlements.countActiveSpaceMembers(orgId, space.id, excluding);\n if (count >= space.capacity) {\n throw new ConflictError('This space is full');\n }\n }\n}\n\nfunction alreadyRegistered(): ConflictError {\n return new ConflictError('You are already going to this event', 'already_registered');\n}\n\nfunction activeNow(grant: Entitlement, now: Date): boolean {\n return (\n grant.status === 'active' &&\n (grant.expiresAt === null || grant.expiresAt.getTime() > now.getTime())\n );\n}\n\nfunction assertOpenForAttendees(event: Event, now: Date): void {\n if (event.status !== 'published') {\n throw new ConflictError('This event is not published', 'not_published');\n }\n if (event.endsAt.getTime() <= now.getTime()) {\n throw new ConflictError('This event has ended', 'ended');\n }\n}\n\nfunction alreadyPlaced(actor: Actor, orgUserId: string): ConflictError {\n return new ConflictError(\n orgUserId === actor.id\n ? 'You already have a place in this space'\n : 'That person already has a place in this space',\n );\n}\n\nfunction ownRowMissing(status: 'invited' | 'active'): ConflictError {\n return new ConflictError(\n status === 'invited'\n ? 'You do not have an invitation to this space'\n : 'You are not a member of this space',\n );\n}\n\nfunction standingFrom(fact: AccessFacts, team: CommunityTeam): Standing {\n const reads = team.readers.has(fact.orgUserId);\n const member = reads || fact.granted;\n return {\n member,\n everySpace: reads || (member && team.moderators.has(fact.orgUserId)),\n activeInSpace: fact.activeInSpace,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { Logger } from "../types/ports.js";
2
+ import { DeviceView, PushConfig, PushDevice } from "../types/notifications.js";
3
+ import "../types/index.js";
4
+ import "../shared/ports.js";
5
+ import { DeviceRegistration, DevicesRepository, DevicesService, MemberActor } from "./ports.js";
6
+ //#region src/identity/devices.d.ts
7
+ type DevicesServiceParams = {
8
+ repo: DevicesRepository;
9
+ push: PushConfig;
10
+ logger?: Logger;
11
+ };
12
+ declare class DevicesServiceImpl implements DevicesService {
13
+ private readonly repo;
14
+ private readonly push;
15
+ private readonly logger;
16
+ constructor(params: DevicesServiceParams);
17
+ register(input: DeviceRegistration, actor: MemberActor): Promise<PushDevice>;
18
+ list(actor: MemberActor): Promise<DeviceView[]>;
19
+ remove(deviceId: string, actor: MemberActor): Promise<void>;
20
+ activeDevices(orgId: string, userId: string, since: Date): Promise<PushDevice[]>;
21
+ withActiveDevices(orgId: string, userIds: string[], since: Date): Promise<string[]>;
22
+ removeDead(orgId: string, deviceIds: string[]): Promise<void>;
23
+ pushConfig(): {
24
+ vapidPublicKey: string | null;
25
+ };
26
+ private registrationProblems;
27
+ }
28
+ //#endregion
29
+ export { DevicesServiceImpl, DevicesServiceParams };
30
+ //# sourceMappingURL=devices.d.ts.map
@@ -0,0 +1,109 @@
1
+ import { noopLogger } from "../shared/logger.js";
2
+ import { NotFoundError, ValidationError } from "../shared/errors.js";
3
+ //#region src/identity/devices.ts
4
+ function pushDevice(device) {
5
+ return {
6
+ id: device.id,
7
+ provider: device.provider,
8
+ token: device.token,
9
+ p256dh: device.p256dh,
10
+ auth: device.auth,
11
+ appId: device.appId,
12
+ environment: device.environment
13
+ };
14
+ }
15
+ var DevicesServiceImpl = class {
16
+ repo;
17
+ push;
18
+ logger;
19
+ constructor(params) {
20
+ this.repo = params.repo;
21
+ this.push = params.push;
22
+ this.logger = params.logger ?? noopLogger;
23
+ }
24
+ async register(input, actor) {
25
+ const orgId = actor.orgId;
26
+ const problems = this.registrationProblems(input);
27
+ if (problems.length > 0) throw new ValidationError(problems);
28
+ const device = await this.repo.upsert(orgId, {
29
+ userId: actor.userId,
30
+ provider: input.provider,
31
+ token: input.token,
32
+ p256dh: input.keys?.p256dh ?? null,
33
+ auth: input.keys?.auth ?? null,
34
+ appId: input.appId ?? null,
35
+ environment: input.environment ?? null
36
+ }, /* @__PURE__ */ new Date());
37
+ this.logger.info("device registered", {
38
+ orgId,
39
+ userId: actor.userId,
40
+ deviceId: device.id,
41
+ provider: device.provider
42
+ });
43
+ return pushDevice(device);
44
+ }
45
+ list(actor) {
46
+ return this.repo.listForUser(actor.orgId, actor.userId, null);
47
+ }
48
+ async remove(deviceId, actor) {
49
+ const orgId = actor.orgId;
50
+ if (!await this.repo.deleteOwn(orgId, actor.userId, deviceId)) throw new NotFoundError("Device", deviceId);
51
+ this.logger.info("device removed", {
52
+ orgId,
53
+ userId: actor.userId,
54
+ deviceId
55
+ });
56
+ }
57
+ async activeDevices(orgId, userId, since) {
58
+ return (await this.repo.listForUser(orgId, userId, since)).map(pushDevice);
59
+ }
60
+ async withActiveDevices(orgId, userIds, since) {
61
+ if (userIds.length === 0) return [];
62
+ return this.repo.usersWithDevices(orgId, [...new Set(userIds)], since);
63
+ }
64
+ async removeDead(orgId, deviceIds) {
65
+ if (deviceIds.length === 0) return;
66
+ await this.repo.deleteMany(orgId, deviceIds);
67
+ this.logger.info("dead devices removed", {
68
+ orgId,
69
+ count: deviceIds.length
70
+ });
71
+ }
72
+ pushConfig() {
73
+ return { vapidPublicKey: this.push.webpush?.publicKey ?? null };
74
+ }
75
+ registrationProblems(input) {
76
+ const problems = [];
77
+ if (!this.push[input.provider]) {
78
+ problems.push({
79
+ path: "provider",
80
+ message: `push through ${input.provider} is not configured`
81
+ });
82
+ return problems;
83
+ }
84
+ if (input.provider === "apns") {
85
+ const bundleIds = this.push.apns?.bundleIds ?? [];
86
+ if (!input.appId || !bundleIds.includes(input.appId)) problems.push({
87
+ path: "appId",
88
+ message: "must be one of the configured iOS bundle ids"
89
+ });
90
+ } else if (input.environment !== void 0) problems.push({
91
+ path: "environment",
92
+ message: "applies to apns devices only"
93
+ });
94
+ if (input.provider === "webpush") {
95
+ if (!input.keys) problems.push({
96
+ path: "keys",
97
+ message: "a webpush device needs its p256dh and auth keys"
98
+ });
99
+ } else if (input.keys !== void 0) problems.push({
100
+ path: "keys",
101
+ message: "apply to webpush devices only"
102
+ });
103
+ return problems;
104
+ }
105
+ };
106
+ //#endregion
107
+ export { DevicesServiceImpl };
108
+
109
+ //# sourceMappingURL=devices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.js","names":[],"sources":["../../src/identity/devices.ts"],"sourcesContent":["import type { DeviceView, PushConfig, PushDevice } from '../types/index.js';\nimport type { Logger } from '../shared/ports.js';\nimport { NotFoundError, ValidationError, type ValidationProblem } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type {\n DeviceRegistration,\n DevicesRepository,\n DevicesService,\n MemberActor,\n} from './ports.js';\n\nfunction pushDevice(device: DeviceView): PushDevice {\n return {\n id: device.id,\n provider: device.provider,\n token: device.token,\n p256dh: device.p256dh,\n auth: device.auth,\n appId: device.appId,\n environment: device.environment,\n };\n}\n\nexport type DevicesServiceParams = {\n repo: DevicesRepository;\n push: PushConfig;\n logger?: Logger;\n};\n\nexport class DevicesServiceImpl implements DevicesService {\n private readonly repo: DevicesRepository;\n private readonly push: PushConfig;\n private readonly logger: Logger;\n\n constructor(params: DevicesServiceParams) {\n this.repo = params.repo;\n this.push = params.push;\n this.logger = params.logger ?? noopLogger;\n }\n\n async register(input: DeviceRegistration, actor: MemberActor): Promise<PushDevice> {\n const orgId = actor.orgId;\n const problems = this.registrationProblems(input);\n if (problems.length > 0) {\n throw new ValidationError(problems);\n }\n const device = await this.repo.upsert(\n orgId,\n {\n userId: actor.userId,\n provider: input.provider,\n token: input.token,\n p256dh: input.keys?.p256dh ?? null,\n auth: input.keys?.auth ?? null,\n appId: input.appId ?? null,\n environment: input.environment ?? null,\n },\n new Date(),\n );\n this.logger.info('device registered', {\n orgId,\n userId: actor.userId,\n deviceId: device.id,\n provider: device.provider,\n });\n return pushDevice(device);\n }\n\n list(actor: MemberActor): Promise<DeviceView[]> {\n return this.repo.listForUser(actor.orgId, actor.userId, null);\n }\n\n async remove(deviceId: string, actor: MemberActor): Promise<void> {\n const orgId = actor.orgId;\n if (!(await this.repo.deleteOwn(orgId, actor.userId, deviceId))) {\n throw new NotFoundError('Device', deviceId);\n }\n this.logger.info('device removed', { orgId, userId: actor.userId, deviceId });\n }\n\n async activeDevices(orgId: string, userId: string, since: Date): Promise<PushDevice[]> {\n const devices = await this.repo.listForUser(orgId, userId, since);\n return devices.map(pushDevice);\n }\n\n async withActiveDevices(orgId: string, userIds: string[], since: Date): Promise<string[]> {\n if (userIds.length === 0) {\n return [];\n }\n return this.repo.usersWithDevices(orgId, [...new Set(userIds)], since);\n }\n\n async removeDead(orgId: string, deviceIds: string[]): Promise<void> {\n if (deviceIds.length === 0) {\n return;\n }\n await this.repo.deleteMany(orgId, deviceIds);\n this.logger.info('dead devices removed', { orgId, count: deviceIds.length });\n }\n\n pushConfig(): { vapidPublicKey: string | null } {\n return { vapidPublicKey: this.push.webpush?.publicKey ?? null };\n }\n\n private registrationProblems(input: DeviceRegistration): ValidationProblem[] {\n const problems: ValidationProblem[] = [];\n if (!this.push[input.provider]) {\n problems.push({\n path: 'provider',\n message: `push through ${input.provider} is not configured`,\n });\n return problems;\n }\n if (input.provider === 'apns') {\n const bundleIds = this.push.apns?.bundleIds ?? [];\n if (!input.appId || !bundleIds.includes(input.appId)) {\n problems.push({\n path: 'appId',\n message: 'must be one of the configured iOS bundle ids',\n });\n }\n } else if (input.environment !== undefined) {\n problems.push({\n path: 'environment',\n message: 'applies to apns devices only',\n });\n }\n if (input.provider === 'webpush') {\n if (!input.keys) {\n problems.push({\n path: 'keys',\n message: 'a webpush device needs its p256dh and auth keys',\n });\n }\n } else if (input.keys !== undefined) {\n problems.push({ path: 'keys', message: 'apply to webpush devices only' });\n }\n return problems;\n }\n}\n"],"mappings":"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"}
@@ -1,10 +1,11 @@
1
- import { CreateUserInput, Notification, NotificationKind, NotificationPreference, ProvisionUserInput, UpdateUserInput, UserProfile, UserProfileView } from "../types/schemas/identity.js";
1
+ import { CreateUserInput, ProvisionUserInput, UpdateUserInput, UserProfile, UserProfileView } from "../types/schemas/identity.js";
2
2
  import { User, UserId } from "../types/identity.js";
3
3
  import "./model.js";
4
4
  import "./types.js";
5
- import { AuthHeaders, IdentityRepository, IdentityService, NewNotification, NotificationDesk, NotificationInbox, NotificationPage, NotificationService, NotificationsQuery, NotificationsRepository, ProfilePatch, SessionAdmin, UpdateProfileInput, UserEditor, UserProvisioner, UserResolver } from "./ports.js";
6
- import { IdentityServiceImpl } from "./service.js";
7
- import { NotificationServiceImpl, defaultPreference } from "./notifications.js";
5
+ import { AuthHeaders, CommunityAccessReader, DeviceRegistration, DevicesRepository, DevicesService, IdentityRepository, IdentityService, MemberActor, NewDevice, NotificationCategoryReader, NotificationSettingsRepository, NotificationSettingsService, ProfilePatch, SessionAdmin, SettingChange, SettingKey, UpdateProfileInput, UserEditor, UserProvisioner, UserResolver } from "./ports.js";
6
+ import { IdentityServiceImpl, UNNAMED_MEMBER } from "./service.js";
7
+ import { NotificationSettingsServiceImpl } from "./notification-settings.js";
8
+ import { DevicesServiceImpl } from "./devices.js";
8
9
  import { IdentityEvent, UserCreated, UserDeleted, UserUpdated, identityEvents } from "./events.js";
9
10
  import { ACCESS_TOKEN_TTL_SECONDS, ORG_CLAIM } from "./access.js";
10
- export { ACCESS_TOKEN_TTL_SECONDS, type AuthHeaders, type CreateUserInput, type IdentityEvent, type IdentityRepository, type IdentityService, IdentityServiceImpl, type NewNotification, type Notification, type NotificationDesk, type NotificationInbox, type NotificationKind, type NotificationPage, type NotificationPreference, type NotificationService, NotificationServiceImpl, type NotificationsQuery, type NotificationsRepository, ORG_CLAIM, type ProfilePatch, type ProvisionUserInput, type SessionAdmin, type UpdateProfileInput, type UpdateUserInput, type User, type UserCreated, type UserDeleted, type UserEditor, type UserId, type UserProfile, type UserProfileView, type UserProvisioner, type UserResolver, type UserUpdated, defaultPreference, identityEvents };
11
+ export { ACCESS_TOKEN_TTL_SECONDS, type AuthHeaders, type CommunityAccessReader, type CreateUserInput, type DeviceRegistration, type DevicesRepository, type DevicesService, DevicesServiceImpl, type IdentityEvent, type IdentityRepository, type IdentityService, IdentityServiceImpl, type MemberActor, type NewDevice, type NotificationCategoryReader, type NotificationSettingsRepository, type NotificationSettingsService, NotificationSettingsServiceImpl, ORG_CLAIM, type ProfilePatch, type ProvisionUserInput, type SessionAdmin, type SettingChange, type SettingKey, UNNAMED_MEMBER, type UpdateProfileInput, type UpdateUserInput, type User, type UserCreated, type UserDeleted, type UserEditor, type UserId, type UserProfile, type UserProfileView, type UserProvisioner, type UserResolver, type UserUpdated, identityEvents };
@@ -1,5 +1,6 @@
1
1
  import { identityEvents } from "./events.js";
2
- import { IdentityServiceImpl } from "./service.js";
3
- import { NotificationServiceImpl, defaultPreference } from "./notifications.js";
2
+ import { IdentityServiceImpl, UNNAMED_MEMBER } from "./service.js";
3
+ import { NotificationSettingsServiceImpl } from "./notification-settings.js";
4
+ import { DevicesServiceImpl } from "./devices.js";
4
5
  import { ACCESS_TOKEN_TTL_SECONDS, ORG_CLAIM } from "./access.js";
5
- export { ACCESS_TOKEN_TTL_SECONDS, IdentityServiceImpl, NotificationServiceImpl, ORG_CLAIM, defaultPreference, identityEvents };
6
+ export { ACCESS_TOKEN_TTL_SECONDS, DevicesServiceImpl, IdentityServiceImpl, NotificationSettingsServiceImpl, ORG_CLAIM, UNNAMED_MEMBER, identityEvents };
@@ -1,4 +1,4 @@
1
- import { Notification, NotificationKind, NotificationPreference, UserProfile, UserProfileView } from "../types/schemas/identity.js";
1
+ import { UserProfile, UserProfileView } from "../types/schemas/identity.js";
2
2
  import { User } from "../types/identity.js";
3
3
  import "../types/index.js";
4
- export type { Notification, NotificationKind, NotificationPreference, User, UserProfile, UserProfileView };
4
+ export type { User, UserProfile, UserProfileView };
@@ -0,0 +1,28 @@
1
+ import { Logger } from "../types/ports.js";
2
+ import { SettingsScope, StoredSetting } from "../types/notifications.js";
3
+ import "../types/index.js";
4
+ import { ChannelRegistry } from "../shared/ports.js";
5
+ import { CommunityAccessReader, MemberActor, NotificationCategoryReader, NotificationSettingsRepository, NotificationSettingsService, SettingChange } from "./ports.js";
6
+ //#region src/identity/notification-settings.d.ts
7
+ type NotificationSettingsServiceParams = {
8
+ repo: NotificationSettingsRepository;
9
+ categories: NotificationCategoryReader;
10
+ access: CommunityAccessReader;
11
+ channels: Pick<ChannelRegistry, 'list'>;
12
+ logger?: Logger;
13
+ };
14
+ declare class NotificationSettingsServiceImpl implements NotificationSettingsService {
15
+ private readonly repo;
16
+ private readonly categories;
17
+ private readonly access;
18
+ private readonly channels;
19
+ private readonly logger;
20
+ constructor(params: NotificationSettingsServiceParams);
21
+ get(orgId: string, userId: string, scope: SettingsScope): Promise<StoredSetting[]>;
22
+ getMany(orgId: string, userIds: string[], scope: SettingsScope, categories: string[]): Promise<Record<string, StoredSetting[]>>;
23
+ set(scope: SettingsScope, changes: SettingChange[], actor: MemberActor): Promise<void>;
24
+ switchedOn(orgId: string, communityId: string, category: string, spaceId: string | null): Promise<string[]>;
25
+ }
26
+ //#endregion
27
+ export { NotificationSettingsServiceImpl, NotificationSettingsServiceParams };
28
+ //# sourceMappingURL=notification-settings.d.ts.map
@@ -0,0 +1,114 @@
1
+ import { noopLogger } from "../shared/logger.js";
2
+ import { NotFoundError, ValidationError } from "../shared/errors.js";
3
+ //#region src/identity/notification-settings.ts
4
+ function communityOf(scope) {
5
+ return scope === "org" ? null : scope.communityId;
6
+ }
7
+ function keyOf(key) {
8
+ return JSON.stringify([
9
+ key.communityId,
10
+ key.spaceId,
11
+ key.category,
12
+ key.channel
13
+ ]);
14
+ }
15
+ var NotificationSettingsServiceImpl = class {
16
+ repo;
17
+ categories;
18
+ access;
19
+ channels;
20
+ logger;
21
+ constructor(params) {
22
+ this.repo = params.repo;
23
+ this.categories = params.categories;
24
+ this.access = params.access;
25
+ this.channels = params.channels;
26
+ this.logger = params.logger ?? noopLogger;
27
+ }
28
+ async get(orgId, userId, scope) {
29
+ return (await this.repo.forUsers(orgId, [userId], communityOf(scope), null)).map(({ userId: _userId, ...setting }) => setting);
30
+ }
31
+ async getMany(orgId, userIds, scope, categories) {
32
+ const unique = [...new Set(userIds)];
33
+ const result = Object.fromEntries(unique.map((userId) => [userId, []]));
34
+ if (unique.length === 0 || categories.length === 0) return result;
35
+ const rows = await this.repo.forUsers(orgId, unique, communityOf(scope), categories);
36
+ for (const { userId, ...setting } of rows) result[userId]?.push(setting);
37
+ return result;
38
+ }
39
+ async set(scope, changes, actor) {
40
+ const orgId = actor.orgId;
41
+ const communityId = communityOf(scope);
42
+ let list;
43
+ if (communityId === null) list = await this.categories.orgList(orgId);
44
+ else {
45
+ if (!await this.access.canOpen(orgId, actor, communityId)) throw new NotFoundError("Community", communityId);
46
+ list = await this.categories.communityList(orgId, communityId);
47
+ }
48
+ const configured = this.channels.list();
49
+ const problems = [];
50
+ const byKey = /* @__PURE__ */ new Map();
51
+ changes.forEach((change, index) => {
52
+ const at = `settings[${index}]`;
53
+ const category = list.find((candidate) => candidate.key === change.category);
54
+ if (!category) {
55
+ problems.push({
56
+ path: `${at}.category`,
57
+ message: `there is no category ${change.category}`
58
+ });
59
+ return;
60
+ }
61
+ const offered = category.channels[change.channel];
62
+ if (!offered || !configured.includes(change.channel)) {
63
+ problems.push({
64
+ path: `${at}.channel`,
65
+ message: `${change.category} does not offer the channel ${change.channel}`
66
+ });
67
+ return;
68
+ }
69
+ if (offered.locked) {
70
+ problems.push({
71
+ path: `${at}.channel`,
72
+ message: `the channel ${change.channel} of ${change.category} is locked`
73
+ });
74
+ return;
75
+ }
76
+ if (change.spaceId !== void 0 && category.perSpace !== true) {
77
+ problems.push({
78
+ path: `${at}.spaceId`,
79
+ message: `${change.category} cannot be set per space`
80
+ });
81
+ return;
82
+ }
83
+ const key = {
84
+ communityId,
85
+ spaceId: change.spaceId ?? null,
86
+ category: change.category,
87
+ channel: change.channel
88
+ };
89
+ byKey.set(keyOf(key), {
90
+ key,
91
+ enabled: change.enabled
92
+ });
93
+ });
94
+ if (problems.length > 0) throw new ValidationError(problems);
95
+ const entries = [...byKey.values()];
96
+ await this.repo.apply(orgId, actor.userId, entries.flatMap(({ key, enabled }) => enabled === null ? [] : [{
97
+ ...key,
98
+ enabled
99
+ }]), entries.flatMap(({ key, enabled }) => enabled === null ? [key] : []), /* @__PURE__ */ new Date());
100
+ this.logger.info("notification settings changed", {
101
+ orgId,
102
+ userId: actor.userId,
103
+ communityId,
104
+ count: entries.length
105
+ });
106
+ }
107
+ switchedOn(orgId, communityId, category, spaceId) {
108
+ return this.repo.switchedOn(orgId, communityId, category, spaceId);
109
+ }
110
+ };
111
+ //#endregion
112
+ export { NotificationSettingsServiceImpl };
113
+
114
+ //# sourceMappingURL=notification-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-settings.js","names":[],"sources":["../../src/identity/notification-settings.ts"],"sourcesContent":["import type { NotificationCategory, SettingsScope, StoredSetting } from '../types/index.js';\nimport type { ChannelRegistry, Logger } from '../shared/ports.js';\nimport { NotFoundError, ValidationError, type ValidationProblem } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type {\n CommunityAccessReader,\n MemberActor,\n NotificationCategoryReader,\n NotificationSettingsRepository,\n NotificationSettingsService,\n SettingChange,\n SettingKey,\n} from './ports.js';\n\nfunction communityOf(scope: SettingsScope): string | null {\n return scope === 'org' ? null : scope.communityId;\n}\n\nfunction keyOf(key: SettingKey): string {\n return JSON.stringify([key.communityId, key.spaceId, key.category, key.channel]);\n}\n\nexport type NotificationSettingsServiceParams = {\n repo: NotificationSettingsRepository;\n categories: NotificationCategoryReader;\n access: CommunityAccessReader;\n channels: Pick<ChannelRegistry, 'list'>;\n logger?: Logger;\n};\n\nexport class NotificationSettingsServiceImpl implements NotificationSettingsService {\n private readonly repo: NotificationSettingsRepository;\n private readonly categories: NotificationCategoryReader;\n private readonly access: CommunityAccessReader;\n private readonly channels: Pick<ChannelRegistry, 'list'>;\n private readonly logger: Logger;\n\n constructor(params: NotificationSettingsServiceParams) {\n this.repo = params.repo;\n this.categories = params.categories;\n this.access = params.access;\n this.channels = params.channels;\n this.logger = params.logger ?? noopLogger;\n }\n\n async get(orgId: string, userId: string, scope: SettingsScope): Promise<StoredSetting[]> {\n const rows = await this.repo.forUsers(orgId, [userId], communityOf(scope), null);\n return rows.map(({ userId: _userId, ...setting }) => setting);\n }\n\n async getMany(\n orgId: string,\n userIds: string[],\n scope: SettingsScope,\n categories: string[],\n ): Promise<Record<string, StoredSetting[]>> {\n const unique = [...new Set(userIds)];\n const result: Record<string, StoredSetting[]> = Object.fromEntries(\n unique.map((userId) => [userId, []]),\n );\n if (unique.length === 0 || categories.length === 0) {\n return result;\n }\n const rows = await this.repo.forUsers(orgId, unique, communityOf(scope), categories);\n for (const { userId, ...setting } of rows) {\n result[userId]?.push(setting);\n }\n return result;\n }\n\n async set(scope: SettingsScope, changes: SettingChange[], actor: MemberActor): Promise<void> {\n const orgId = actor.orgId;\n const communityId = communityOf(scope);\n let list: NotificationCategory[];\n if (communityId === null) {\n list = await this.categories.orgList(orgId);\n } else {\n if (!(await this.access.canOpen(orgId, actor, communityId))) {\n throw new NotFoundError('Community', communityId);\n }\n list = await this.categories.communityList(orgId, communityId);\n }\n const configured = this.channels.list();\n const problems: ValidationProblem[] = [];\n const byKey = new Map<string, { key: SettingKey; enabled: boolean | null }>();\n changes.forEach((change, index) => {\n const at = `settings[${index}]`;\n const category = list.find((candidate) => candidate.key === change.category);\n if (!category) {\n problems.push({\n path: `${at}.category`,\n message: `there is no category ${change.category}`,\n });\n return;\n }\n const offered = category.channels[change.channel];\n if (!offered || !configured.includes(change.channel)) {\n problems.push({\n path: `${at}.channel`,\n message: `${change.category} does not offer the channel ${change.channel}`,\n });\n return;\n }\n if (offered.locked) {\n problems.push({\n path: `${at}.channel`,\n message: `the channel ${change.channel} of ${change.category} is locked`,\n });\n return;\n }\n if (change.spaceId !== undefined && category.perSpace !== true) {\n problems.push({\n path: `${at}.spaceId`,\n message: `${change.category} cannot be set per space`,\n });\n return;\n }\n const key: SettingKey = {\n communityId,\n spaceId: change.spaceId ?? null,\n category: change.category,\n channel: change.channel,\n };\n byKey.set(keyOf(key), { key, enabled: change.enabled });\n });\n if (problems.length > 0) {\n throw new ValidationError(problems);\n }\n const entries = [...byKey.values()];\n await this.repo.apply(\n orgId,\n actor.userId,\n entries.flatMap(({ key, enabled }) => (enabled === null ? [] : [{ ...key, enabled }])),\n entries.flatMap(({ key, enabled }) => (enabled === null ? [key] : [])),\n new Date(),\n );\n this.logger.info('notification settings changed', {\n orgId,\n userId: actor.userId,\n communityId,\n count: entries.length,\n });\n }\n\n switchedOn(\n orgId: string,\n communityId: string,\n category: string,\n spaceId: string | null,\n ): Promise<string[]> {\n return this.repo.switchedOn(orgId, communityId, category, spaceId);\n }\n}\n"],"mappings":"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"}