@headless-lms/core 0.4.0 → 0.6.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 (249) hide show
  1. package/dist/assets/events.d.ts +15 -9
  2. package/dist/assets/file-urls.d.ts +14 -7
  3. package/dist/assets/file-urls.js +15 -4
  4. package/dist/assets/file-urls.js.map +1 -1
  5. package/dist/assets/index.d.ts +4 -4
  6. package/dist/assets/index.js +3 -3
  7. package/dist/assets/ports.d.ts +34 -15
  8. package/dist/assets/service.d.ts +30 -14
  9. package/dist/assets/service.js +233 -54
  10. package/dist/assets/service.js.map +1 -1
  11. package/dist/automations/actions.js.map +1 -1
  12. package/dist/automations/catalog.js +68 -3
  13. package/dist/automations/catalog.js.map +1 -1
  14. package/dist/automations/community-derivations.js +16 -16
  15. package/dist/automations/community-derivations.js.map +1 -1
  16. package/dist/automations/community-mail.d.ts +34 -4
  17. package/dist/automations/community-mail.js +32 -4
  18. package/dist/automations/community-mail.js.map +1 -1
  19. package/dist/automations/event-permissions.d.ts +12 -0
  20. package/dist/automations/event-permissions.js +38 -0
  21. package/dist/automations/event-permissions.js.map +1 -0
  22. package/dist/automations/index.d.ts +2 -1
  23. package/dist/automations/index.js +2 -1
  24. package/dist/automations/notifications/applicable.d.ts +8 -0
  25. package/dist/automations/notifications/applicable.js +12 -0
  26. package/dist/automations/notifications/applicable.js.map +1 -0
  27. package/dist/automations/notifications/debounce.js +1 -1
  28. package/dist/automations/notifications/debounce.js.map +1 -1
  29. package/dist/automations/notifications/defaults.js +33 -3
  30. package/dist/automations/notifications/defaults.js.map +1 -1
  31. package/dist/automations/notifications/processor.js +1 -1
  32. package/dist/automations/notifications/processor.js.map +1 -1
  33. package/dist/automations/notifications/rules/content.js +60 -3
  34. package/dist/automations/notifications/rules/content.js.map +1 -1
  35. package/dist/automations/notifications/rules/discussion.js +29 -29
  36. package/dist/automations/notifications/rules/discussion.js.map +1 -1
  37. package/dist/automations/notifications/rules/entitlements.js +36 -9
  38. package/dist/automations/notifications/rules/entitlements.js.map +1 -1
  39. package/dist/automations/notifications/rules/events.d.ts +9 -0
  40. package/dist/automations/notifications/rules/events.js +27 -0
  41. package/dist/automations/notifications/rules/events.js.map +1 -0
  42. package/dist/automations/notifications/rules/organizations.js +17 -10
  43. package/dist/automations/notifications/rules/organizations.js.map +1 -1
  44. package/dist/automations/notifications/rules/recipients.js +0 -1
  45. package/dist/automations/notifications/rules/recipients.js.map +1 -1
  46. package/dist/automations/notifications/service.d.ts +8 -6
  47. package/dist/automations/notifications/service.js +12 -12
  48. package/dist/automations/notifications/service.js.map +1 -1
  49. package/dist/automations/notifications/types.js +94 -7
  50. package/dist/automations/notifications/types.js.map +1 -1
  51. package/dist/automations/ports.d.ts +30 -31
  52. package/dist/automations/service.d.ts +20 -17
  53. package/dist/automations/service.js +90 -61
  54. package/dist/automations/service.js.map +1 -1
  55. package/dist/content/event-sight.d.ts +11 -0
  56. package/dist/content/event-sight.js +11 -0
  57. package/dist/content/event-sight.js.map +1 -0
  58. package/dist/content/events.d.ts +465 -88
  59. package/dist/content/events.js +34 -1
  60. package/dist/content/events.js.map +1 -1
  61. package/dist/content/index.d.ts +6 -4
  62. package/dist/content/index.js +3 -1
  63. package/dist/content/jobs.d.ts +16 -0
  64. package/dist/content/jobs.js +37 -0
  65. package/dist/content/jobs.js.map +1 -0
  66. package/dist/content/model.d.ts +2 -2
  67. package/dist/content/notification-categories.d.ts +5 -4
  68. package/dist/content/notification-categories.js +15 -6
  69. package/dist/content/notification-categories.js.map +1 -1
  70. package/dist/content/ports.d.ts +113 -68
  71. package/dist/content/service.d.ts +103 -67
  72. package/dist/content/service.js +739 -152
  73. package/dist/content/service.js.map +1 -1
  74. package/dist/content/titles.d.ts +6 -3
  75. package/dist/content/titles.js +37 -24
  76. package/dist/content/titles.js.map +1 -1
  77. package/dist/content/types.d.ts +2 -2
  78. package/dist/discussion/authors.d.ts +4 -2
  79. package/dist/discussion/authors.js +9 -5
  80. package/dist/discussion/authors.js.map +1 -1
  81. package/dist/discussion/chats.d.ts +56 -37
  82. package/dist/discussion/chats.js +392 -160
  83. package/dist/discussion/chats.js.map +1 -1
  84. package/dist/discussion/events.d.ts +85 -47
  85. package/dist/discussion/events.js +41 -3
  86. package/dist/discussion/events.js.map +1 -1
  87. package/dist/discussion/index.d.ts +8 -5
  88. package/dist/discussion/index.js +5 -3
  89. package/dist/discussion/live.d.ts +106 -0
  90. package/dist/discussion/live.js +642 -0
  91. package/dist/discussion/live.js.map +1 -0
  92. package/dist/discussion/mentions.js +2 -2
  93. package/dist/discussion/mentions.js.map +1 -1
  94. package/dist/discussion/ports.d.ts +182 -100
  95. package/dist/discussion/ports.js.map +1 -1
  96. package/dist/discussion/posts.d.ts +45 -39
  97. package/dist/discussion/posts.js +266 -152
  98. package/dist/discussion/posts.js.map +1 -1
  99. package/dist/discussion/rules.d.ts +18 -0
  100. package/dist/discussion/rules.js +22 -0
  101. package/dist/discussion/rules.js.map +1 -0
  102. package/dist/discussion/service.d.ts +37 -31
  103. package/dist/discussion/service.js +250 -186
  104. package/dist/discussion/service.js.map +1 -1
  105. package/dist/discussion/subscriptions.d.ts +8 -8
  106. package/dist/discussion/subscriptions.js +28 -12
  107. package/dist/discussion/subscriptions.js.map +1 -1
  108. package/dist/discussion/titles.d.ts +4 -2
  109. package/dist/discussion/titles.js +27 -13
  110. package/dist/discussion/titles.js.map +1 -1
  111. package/dist/entitlements/events.d.ts +58 -58
  112. package/dist/entitlements/index.d.ts +4 -4
  113. package/dist/entitlements/index.js +2 -2
  114. package/dist/entitlements/model.d.ts +2 -2
  115. package/dist/entitlements/ports.d.ts +65 -39
  116. package/dist/entitlements/rules.d.ts +3 -6
  117. package/dist/entitlements/rules.js +5 -10
  118. package/dist/entitlements/rules.js.map +1 -1
  119. package/dist/entitlements/service.d.ts +40 -28
  120. package/dist/entitlements/service.js +369 -162
  121. package/dist/entitlements/service.js.map +1 -1
  122. package/dist/identity/devices.d.ts +3 -3
  123. package/dist/identity/devices.js +6 -4
  124. package/dist/identity/devices.js.map +1 -1
  125. package/dist/identity/index.d.ts +2 -2
  126. package/dist/identity/index.js +2 -2
  127. package/dist/identity/notification-settings.d.ts +1 -1
  128. package/dist/identity/notification-settings.js +2 -1
  129. package/dist/identity/notification-settings.js.map +1 -1
  130. package/dist/identity/ports.d.ts +7 -7
  131. package/dist/identity/service.d.ts +8 -7
  132. package/dist/identity/service.js +16 -12
  133. package/dist/identity/service.js.map +1 -1
  134. package/dist/integrations/events.d.ts +3 -3
  135. package/dist/integrations/ports.d.ts +9 -8
  136. package/dist/integrations/service.d.ts +9 -8
  137. package/dist/integrations/service.js +31 -14
  138. package/dist/integrations/service.js.map +1 -1
  139. package/dist/organizations/default-roles.d.ts +22 -0
  140. package/dist/organizations/default-roles.js +89 -0
  141. package/dist/organizations/default-roles.js.map +1 -0
  142. package/dist/organizations/effective-permissions.d.ts +47 -0
  143. package/dist/organizations/effective-permissions.js +118 -0
  144. package/dist/organizations/effective-permissions.js.map +1 -0
  145. package/dist/organizations/events.d.ts +326 -181
  146. package/dist/organizations/events.js +90 -18
  147. package/dist/organizations/events.js.map +1 -1
  148. package/dist/organizations/index.d.ts +14 -10
  149. package/dist/organizations/index.js +7 -3
  150. package/dist/organizations/members.d.ts +66 -8
  151. package/dist/organizations/members.js.map +1 -1
  152. package/dist/organizations/model.d.ts +2 -2
  153. package/dist/organizations/notification-catalog.d.ts +31 -0
  154. package/dist/organizations/notification-catalog.js +32 -0
  155. package/dist/organizations/notification-catalog.js.map +1 -0
  156. package/dist/organizations/notification-categories.d.ts +6 -4
  157. package/dist/organizations/notification-categories.js +25 -10
  158. package/dist/organizations/notification-categories.js.map +1 -1
  159. package/dist/organizations/org-roles.d.ts +33 -0
  160. package/dist/organizations/org-roles.js +49 -0
  161. package/dist/organizations/org-roles.js.map +1 -0
  162. package/dist/organizations/permissions.d.ts +311 -0
  163. package/dist/organizations/permissions.js +327 -0
  164. package/dist/organizations/permissions.js.map +1 -0
  165. package/dist/organizations/ports.d.ts +187 -65
  166. package/dist/organizations/service.d.ts +102 -35
  167. package/dist/organizations/service.js +1254 -327
  168. package/dist/organizations/service.js.map +1 -1
  169. package/dist/organizations/testing.d.ts +18 -0
  170. package/dist/organizations/testing.js +37 -0
  171. package/dist/organizations/testing.js.map +1 -0
  172. package/dist/organizations/types.d.ts +5 -16
  173. package/dist/progress/events.d.ts +14 -14
  174. package/dist/progress/index.d.ts +2 -2
  175. package/dist/progress/ports.d.ts +11 -3
  176. package/dist/progress/service.d.ts +6 -5
  177. package/dist/progress/service.js +21 -6
  178. package/dist/progress/service.js.map +1 -1
  179. package/dist/reporting/admin/index.d.ts +6 -4
  180. package/dist/reporting/admin/model.d.ts +36 -3
  181. package/dist/reporting/admin/ports.d.ts +39 -14
  182. package/dist/reporting/admin/service.d.ts +32 -11
  183. package/dist/reporting/admin/service.js +102 -18
  184. package/dist/reporting/admin/service.js.map +1 -1
  185. package/dist/reporting/events/index.d.ts +27 -0
  186. package/dist/reporting/events/index.js +20 -0
  187. package/dist/reporting/events/index.js.map +1 -0
  188. package/dist/reporting/member/calendar.d.ts +11 -0
  189. package/dist/reporting/member/calendar.js +75 -0
  190. package/dist/reporting/member/calendar.js.map +1 -0
  191. package/dist/reporting/member/index.d.ts +3 -2
  192. package/dist/reporting/member/model.d.ts +29 -5
  193. package/dist/reporting/member/ports.d.ts +49 -29
  194. package/dist/reporting/member/service.d.ts +57 -29
  195. package/dist/reporting/member/service.js +345 -114
  196. package/dist/reporting/member/service.js.map +1 -1
  197. package/dist/shared/actor.d.ts +23 -3
  198. package/dist/shared/errors.d.ts +2 -1
  199. package/dist/shared/errors.js +3 -1
  200. package/dist/shared/errors.js.map +1 -1
  201. package/dist/shared/id.d.ts +4 -0
  202. package/dist/shared/id.js +4 -0
  203. package/dist/shared/id.js.map +1 -1
  204. package/dist/shared/logger.d.ts +2 -1
  205. package/dist/shared/logger.js +10 -1
  206. package/dist/shared/logger.js.map +1 -1
  207. package/dist/shared/ports.d.ts +25 -5
  208. package/dist/types/content.d.ts +2 -2
  209. package/dist/types/discussion.d.ts +4 -5
  210. package/dist/types/email-templates.d.ts +22 -3
  211. package/dist/types/entitlements.d.ts +2 -2
  212. package/dist/types/identity.d.ts +13 -16
  213. package/dist/types/index.d.ts +9 -9
  214. package/dist/types/index.js +2 -1
  215. package/dist/types/notifications.d.ts +4 -4
  216. package/dist/types/organizations.d.ts +3 -2
  217. package/dist/types/ports.d.ts +8 -1
  218. package/dist/types/ports.js +7 -1
  219. package/dist/types/ports.js.map +1 -0
  220. package/dist/types/schemas/assets.d.ts +5 -5
  221. package/dist/types/schemas/assets.js +4 -3
  222. package/dist/types/schemas/assets.js.map +1 -1
  223. package/dist/types/schemas/content.d.ts +599 -13
  224. package/dist/types/schemas/content.js +219 -8
  225. package/dist/types/schemas/content.js.map +1 -1
  226. package/dist/types/schemas/discussion.d.ts +280 -57
  227. package/dist/types/schemas/discussion.js +77 -3
  228. package/dist/types/schemas/discussion.js.map +1 -1
  229. package/dist/types/schemas/entitlements.d.ts +21 -8
  230. package/dist/types/schemas/entitlements.js +11 -4
  231. package/dist/types/schemas/entitlements.js.map +1 -1
  232. package/dist/types/schemas/index.d.ts +5 -5
  233. package/dist/types/schemas/index.js +5 -5
  234. package/dist/types/schemas/notifications.d.ts +11 -10
  235. package/dist/types/schemas/notifications.js +3 -2
  236. package/dist/types/schemas/notifications.js.map +1 -1
  237. package/dist/types/schemas/organizations.d.ts +99 -60
  238. package/dist/types/schemas/organizations.js +80 -27
  239. package/dist/types/schemas/organizations.js.map +1 -1
  240. package/dist/types/schemas/progress.d.ts +3 -4
  241. package/dist/types/schemas/progress.js +0 -1
  242. package/dist/types/schemas/progress.js.map +1 -1
  243. package/package.json +1 -1
  244. package/dist/organizations/access-scopes.d.ts +0 -19
  245. package/dist/organizations/access-scopes.js +0 -89
  246. package/dist/organizations/access-scopes.js.map +0 -1
  247. package/dist/organizations/roles.d.ts +0 -19
  248. package/dist/organizations/roles.js +0 -82
  249. 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 openCommunityIds(orgId: string, actor: Actor): Promise<string[]> {\n return this.repo.openCommunityIds(orgId, actor.id);\n }\n\n communityMemberIds(orgId: string, communityId: string): Promise<string[]> {\n return this.repo.communityMemberIds(orgId, communityId);\n }\n\n contentHolderIds(orgId: string, contentId: string): Promise<string[]> {\n return this.repo.contentHolderIds(orgId, contentId);\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 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 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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"}
@@ -14,9 +14,9 @@ declare class DevicesServiceImpl implements DevicesService {
14
14
  private readonly push;
15
15
  private readonly logger;
16
16
  constructor(params: DevicesServiceParams);
17
- register(orgId: string, actor: MemberActor, input: DeviceRegistration): Promise<PushDevice>;
18
- list(orgId: string, actor: MemberActor): Promise<DeviceView[]>;
19
- remove(orgId: string, actor: MemberActor, deviceId: string): Promise<void>;
17
+ register(input: DeviceRegistration, actor: MemberActor): Promise<PushDevice>;
18
+ list(actor: MemberActor): Promise<DeviceView[]>;
19
+ remove(deviceId: string, actor: MemberActor): Promise<void>;
20
20
  activeDevices(orgId: string, userId: string, since: Date): Promise<PushDevice[]>;
21
21
  withActiveDevices(orgId: string, userIds: string[], since: Date): Promise<string[]>;
22
22
  removeDead(orgId: string, deviceIds: string[]): Promise<void>;
@@ -21,7 +21,8 @@ var DevicesServiceImpl = class {
21
21
  this.push = params.push;
22
22
  this.logger = params.logger ?? noopLogger;
23
23
  }
24
- async register(orgId, actor, input) {
24
+ async register(input, actor) {
25
+ const orgId = actor.orgId;
25
26
  const problems = this.registrationProblems(input);
26
27
  if (problems.length > 0) throw new ValidationError(problems);
27
28
  const device = await this.repo.upsert(orgId, {
@@ -41,10 +42,11 @@ var DevicesServiceImpl = class {
41
42
  });
42
43
  return pushDevice(device);
43
44
  }
44
- list(orgId, actor) {
45
- return this.repo.listForUser(orgId, actor.userId, null);
45
+ list(actor) {
46
+ return this.repo.listForUser(actor.orgId, actor.userId, null);
46
47
  }
47
- async remove(orgId, actor, deviceId) {
48
+ async remove(deviceId, actor) {
49
+ const orgId = actor.orgId;
48
50
  if (!await this.repo.deleteOwn(orgId, actor.userId, deviceId)) throw new NotFoundError("Device", deviceId);
49
51
  this.logger.info("device removed", {
50
52
  orgId,
@@ -1 +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(\n orgId: string,\n actor: MemberActor,\n input: DeviceRegistration,\n ): Promise<PushDevice> {\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(orgId: string, actor: MemberActor): Promise<DeviceView[]> {\n return this.repo.listForUser(orgId, actor.userId, null);\n }\n\n async remove(orgId: string, actor: MemberActor, deviceId: string): Promise<void> {\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
+ {"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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"}
@@ -3,9 +3,9 @@ import { User, UserId } from "../types/identity.js";
3
3
  import "./model.js";
4
4
  import "./types.js";
5
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 } from "./service.js";
6
+ import { IdentityServiceImpl, UNNAMED_MEMBER } from "./service.js";
7
7
  import { NotificationSettingsServiceImpl } from "./notification-settings.js";
8
8
  import { DevicesServiceImpl } from "./devices.js";
9
9
  import { IdentityEvent, UserCreated, UserDeleted, UserUpdated, identityEvents } from "./events.js";
10
10
  import { ACCESS_TOKEN_TTL_SECONDS, ORG_CLAIM } from "./access.js";
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, 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 };
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,6 +1,6 @@
1
1
  import { identityEvents } from "./events.js";
2
- import { IdentityServiceImpl } from "./service.js";
2
+ import { IdentityServiceImpl, UNNAMED_MEMBER } from "./service.js";
3
3
  import { NotificationSettingsServiceImpl } from "./notification-settings.js";
4
4
  import { DevicesServiceImpl } from "./devices.js";
5
5
  import { ACCESS_TOKEN_TTL_SECONDS, ORG_CLAIM } from "./access.js";
6
- export { ACCESS_TOKEN_TTL_SECONDS, DevicesServiceImpl, IdentityServiceImpl, NotificationSettingsServiceImpl, ORG_CLAIM, identityEvents };
6
+ export { ACCESS_TOKEN_TTL_SECONDS, DevicesServiceImpl, IdentityServiceImpl, NotificationSettingsServiceImpl, ORG_CLAIM, UNNAMED_MEMBER, identityEvents };
@@ -20,7 +20,7 @@ declare class NotificationSettingsServiceImpl implements NotificationSettingsSer
20
20
  constructor(params: NotificationSettingsServiceParams);
21
21
  get(orgId: string, userId: string, scope: SettingsScope): Promise<StoredSetting[]>;
22
22
  getMany(orgId: string, userIds: string[], scope: SettingsScope, categories: string[]): Promise<Record<string, StoredSetting[]>>;
23
- set(orgId: string, actor: MemberActor, scope: SettingsScope, changes: SettingChange[]): Promise<void>;
23
+ set(scope: SettingsScope, changes: SettingChange[], actor: MemberActor): Promise<void>;
24
24
  switchedOn(orgId: string, communityId: string, category: string, spaceId: string | null): Promise<string[]>;
25
25
  }
26
26
  //#endregion
@@ -36,7 +36,8 @@ var NotificationSettingsServiceImpl = class {
36
36
  for (const { userId, ...setting } of rows) result[userId]?.push(setting);
37
37
  return result;
38
38
  }
39
- async set(orgId, actor, scope, changes) {
39
+ async set(scope, changes, actor) {
40
+ const orgId = actor.orgId;
40
41
  const communityId = communityOf(scope);
41
42
  let list;
42
43
  if (communityId === null) list = await this.categories.orgList(orgId);
@@ -1 +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(\n orgId: string,\n actor: MemberActor,\n scope: SettingsScope,\n changes: SettingChange[],\n ): Promise<void> {\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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"}
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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"}
@@ -3,8 +3,8 @@ import { CreateUserInput, UpdateUserInput, UserProfile, UserProfileView } from "
3
3
  import { User } from "../types/identity.js";
4
4
  import { DeviceView, NotificationCategory, PushDevice, PushProvider, SettingsScope, StoredSetting } from "../types/notifications.js";
5
5
  import "../types/index.js";
6
- import { OutboxAppender, UnitOfWork } from "../shared/ports.js";
7
6
  import { Actor } from "../shared/actor.js";
7
+ import { OutboxAppender, UnitOfWork } from "../shared/ports.js";
8
8
  import "./model.js";
9
9
  import "./types.js";
10
10
  //#region src/identity/ports.d.ts
@@ -58,9 +58,9 @@ interface SessionAdmin {
58
58
  stampActiveOrganization(headers: AuthHeaders, orgId: string): Promise<boolean>;
59
59
  }
60
60
  interface IdentityService extends UserProvisioner, UserResolver, UserEditor {
61
- profileOf(orgId: string, userId: string): Promise<UserProfileView>;
61
+ profileOf(actor: MemberActor): Promise<UserProfileView>;
62
62
  displayName(userId: string | null): Promise<string>;
63
- updateProfile(orgId: string, userId: string, actorUserId: string, orgUserId: string, patch: UpdateProfileInput): Promise<UserProfileView>;
63
+ updateProfile(patch: UpdateProfileInput, actor: MemberActor): Promise<UserProfileView>;
64
64
  }
65
65
  interface IdentityRepository {
66
66
  insertUser(input: CreateUserInput): Promise<User>;
@@ -88,7 +88,7 @@ interface SettingChange {
88
88
  interface NotificationSettingsService {
89
89
  get(orgId: string, userId: string, scope: SettingsScope): Promise<StoredSetting[]>;
90
90
  getMany(orgId: string, userIds: string[], scope: SettingsScope, categories: string[]): Promise<Record<string, StoredSetting[]>>;
91
- set(orgId: string, actor: MemberActor, scope: SettingsScope, changes: SettingChange[]): Promise<void>;
91
+ set(scope: SettingsScope, changes: SettingChange[], actor: MemberActor): Promise<void>;
92
92
  switchedOn(orgId: string, communityId: string, category: string, spaceId: string | null): Promise<string[]>;
93
93
  }
94
94
  interface NotificationCategoryReader {
@@ -124,9 +124,9 @@ interface DeviceRegistration {
124
124
  environment?: 'production' | 'sandbox';
125
125
  }
126
126
  interface DevicesService {
127
- register(orgId: string, actor: MemberActor, input: DeviceRegistration): Promise<PushDevice>;
128
- list(orgId: string, actor: MemberActor): Promise<DeviceView[]>;
129
- remove(orgId: string, actor: MemberActor, deviceId: string): Promise<void>;
127
+ register(input: DeviceRegistration, actor: MemberActor): Promise<PushDevice>;
128
+ list(actor: MemberActor): Promise<DeviceView[]>;
129
+ remove(deviceId: string, actor: MemberActor): Promise<void>;
130
130
  activeDevices(orgId: string, userId: string, since: Date): Promise<PushDevice[]>;
131
131
  withActiveDevices(orgId: string, userIds: string[], since: Date): Promise<string[]>;
132
132
  removeDead(orgId: string, deviceIds: string[]): Promise<void>;
@@ -3,13 +3,14 @@ import { CreateUserInput, UpdateUserInput, UserProfileView } from "../types/sche
3
3
  import { User } from "../types/identity.js";
4
4
  import "../shared/ports.js";
5
5
  import { FileUrls } from "../assets/file-urls.js";
6
- import { MemberImageReader } from "../assets/ports.js";
6
+ import { AssetFiles, MemberImageReader } from "../assets/ports.js";
7
7
  import "../assets/index.js";
8
+ import { Mailer } from "../shared/mailer.js";
8
9
  import "./model.js";
9
10
  import "./types.js";
10
- import { IdentityRepository, IdentityService, IdentityUnitOfWork, UpdateProfileInput } from "./ports.js";
11
- import { Mailer } from "../shared/mailer.js";
11
+ import { IdentityRepository, IdentityService, IdentityUnitOfWork, MemberActor, UpdateProfileInput } from "./ports.js";
12
12
  //#region src/identity/service.d.ts
13
+ declare const UNNAMED_MEMBER = "A member";
13
14
  declare class IdentityServiceImpl implements IdentityService {
14
15
  private readonly repo;
15
16
  private readonly logger;
@@ -22,7 +23,7 @@ declare class IdentityServiceImpl implements IdentityService {
22
23
  logger?: Logger;
23
24
  mailer: Mailer;
24
25
  uow: IdentityUnitOfWork;
25
- images: MemberImageReader;
26
+ images: MemberImageReader & Pick<AssetFiles, 'syncPublicImages'>;
26
27
  fileUrls: FileUrls;
27
28
  });
28
29
  createUser(input: CreateUserInput): Promise<User>;
@@ -42,11 +43,11 @@ declare class IdentityServiceImpl implements IdentityService {
42
43
  email: string;
43
44
  url: string;
44
45
  }): Promise<void>;
45
- profileOf(orgId: string, userId: string): Promise<UserProfileView>;
46
+ profileOf(actor: MemberActor): Promise<UserProfileView>;
46
47
  private view;
47
48
  displayName(userId: string | null): Promise<string>;
48
- updateProfile(orgId: string, userId: string, actorUserId: string, orgUserId: string, patch: UpdateProfileInput): Promise<UserProfileView>;
49
+ updateProfile(patch: UpdateProfileInput, actor: MemberActor): Promise<UserProfileView>;
49
50
  }
50
51
  //#endregion
51
- export { IdentityServiceImpl };
52
+ export { IdentityServiceImpl, UNNAMED_MEMBER };
52
53
  //# sourceMappingURL=service.d.ts.map
@@ -1,9 +1,9 @@
1
1
  import { genId } from "../shared/id.js";
2
- import { ConflictError, ForbiddenError, NotFoundError } from "../shared/errors.js";
2
+ import { ConflictError, NotFoundError } from "../shared/errors.js";
3
3
  import { noopLogger } from "../shared/logger.js";
4
4
  import { identityEvents } from "./events.js";
5
5
  //#region src/identity/service.ts
6
- const UNNAMED = "A member";
6
+ const UNNAMED_MEMBER = "A member";
7
7
  var IdentityServiceImpl = class {
8
8
  repo;
9
9
  logger;
@@ -88,9 +88,10 @@ var IdentityServiceImpl = class {
88
88
  async sendMagicLink(input) {
89
89
  await this.mailer.send(input.email, "magicLink", { url: input.url });
90
90
  }
91
- async profileOf(orgId, userId) {
91
+ async profileOf(actor) {
92
+ const userId = actor.userId;
92
93
  const stored = await this.repo.findProfile(userId);
93
- if (stored) return this.view(orgId, stored);
94
+ if (stored) return this.view(actor.orgId, actor.id, stored);
94
95
  const never = /* @__PURE__ */ new Date(0);
95
96
  return {
96
97
  userId,
@@ -105,22 +106,24 @@ var IdentityServiceImpl = class {
105
106
  updatedAt: never
106
107
  };
107
108
  }
108
- view(orgId, profile) {
109
+ view(orgId, orgUserId, profile) {
109
110
  const imageAssetId = profile.imageAssetOrgId === orgId ? profile.imageAssetId : null;
110
111
  return {
111
112
  ...profile,
112
- imageUrl: imageAssetId ? this.fileUrls.memberUpload(imageAssetId) : null
113
+ imageUrl: imageAssetId ? this.fileUrls.publicImage(orgId, orgUserId, imageAssetId) : null
113
114
  };
114
115
  }
115
116
  async displayName(userId) {
116
- if (userId === null) return UNNAMED;
117
+ if (userId === null) return UNNAMED_MEMBER;
117
118
  const [profile, user] = await Promise.all([this.repo.findProfile(userId), this.repo.findUserById(userId)]);
118
119
  const named = profile?.displayName?.trim();
119
120
  if (named) return named;
120
- return [user?.firstName, user?.lastName].filter(Boolean).join(" ").trim() || UNNAMED;
121
+ return [user?.firstName, user?.lastName].filter(Boolean).join(" ").trim() || "A member";
121
122
  }
122
- async updateProfile(orgId, userId, actorUserId, orgUserId, patch) {
123
- if (actorUserId !== userId) throw new ForbiddenError("You can only update your own profile");
123
+ async updateProfile(patch, actor) {
124
+ const orgId = actor.orgId;
125
+ const userId = actor.userId;
126
+ const orgUserId = actor.id;
124
127
  if (!await this.repo.findUserById(userId)) throw new NotFoundError("User", userId);
125
128
  if (patch.imageAssetId != null) {
126
129
  if (!await this.images.ownsImage(orgId, patch.imageAssetId, orgUserId)) {
@@ -138,10 +141,11 @@ var IdentityServiceImpl = class {
138
141
  imageAssetOrgId
139
142
  });
140
143
  this.logger.info("profile updated", { userId });
141
- return this.view(orgId, profile);
144
+ if (patch.imageAssetId !== void 0) await this.images.syncPublicImages(orgId, orgUserId, profile.imageAssetOrgId === orgId && profile.imageAssetId ? [profile.imageAssetId] : []);
145
+ return this.view(orgId, orgUserId, profile);
142
146
  }
143
147
  };
144
148
  //#endregion
145
- export { IdentityServiceImpl };
149
+ export { IdentityServiceImpl, UNNAMED_MEMBER };
146
150
 
147
151
  //# sourceMappingURL=service.js.map