@headless-lms/core 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/dist/assets/events.d.ts +24 -18
  2. package/dist/assets/file-urls.d.ts +11 -7
  3. package/dist/assets/file-urls.js +14 -4
  4. package/dist/assets/file-urls.js.map +1 -1
  5. package/dist/assets/index.d.ts +5 -4
  6. package/dist/assets/index.js +4 -3
  7. package/dist/assets/jobs.d.ts +15 -0
  8. package/dist/assets/jobs.js +30 -0
  9. package/dist/assets/jobs.js.map +1 -0
  10. package/dist/assets/ports.d.ts +37 -17
  11. package/dist/assets/service.d.ts +30 -15
  12. package/dist/assets/service.js +229 -59
  13. package/dist/assets/service.js.map +1 -1
  14. package/dist/automations/actions.js +2 -63
  15. package/dist/automations/actions.js.map +1 -1
  16. package/dist/automations/catalog.d.ts +1 -2
  17. package/dist/automations/catalog.js +137 -65
  18. package/dist/automations/catalog.js.map +1 -1
  19. package/dist/automations/community-derivations.js +20 -20
  20. package/dist/automations/community-derivations.js.map +1 -1
  21. package/dist/automations/community-mail.d.ts +36 -20
  22. package/dist/automations/community-mail.js +32 -24
  23. package/dist/automations/community-mail.js.map +1 -1
  24. package/dist/automations/event-permissions.d.ts +12 -0
  25. package/dist/automations/event-permissions.js +38 -0
  26. package/dist/automations/event-permissions.js.map +1 -0
  27. package/dist/automations/events.d.ts +0 -10
  28. package/dist/automations/index.d.ts +12 -4
  29. package/dist/automations/index.js +9 -1
  30. package/dist/automations/notifications/applicable.d.ts +8 -0
  31. package/dist/automations/notifications/applicable.js +12 -0
  32. package/dist/automations/notifications/applicable.js.map +1 -0
  33. package/dist/automations/notifications/debounce.d.ts +25 -0
  34. package/dist/automations/notifications/debounce.js +93 -0
  35. package/dist/automations/notifications/debounce.js.map +1 -0
  36. package/dist/automations/notifications/defaults.d.ts +8 -0
  37. package/dist/automations/notifications/defaults.js +346 -0
  38. package/dist/automations/notifications/defaults.js.map +1 -0
  39. package/dist/automations/notifications/delivery.d.ts +28 -0
  40. package/dist/automations/notifications/delivery.js +88 -0
  41. package/dist/automations/notifications/delivery.js.map +1 -0
  42. package/dist/automations/notifications/processor.d.ts +64 -0
  43. package/dist/automations/notifications/processor.js +270 -0
  44. package/dist/automations/notifications/processor.js.map +1 -0
  45. package/dist/automations/notifications/rules/content.d.ts +6 -0
  46. package/dist/automations/notifications/rules/content.js +96 -0
  47. package/dist/automations/notifications/rules/content.js.map +1 -0
  48. package/dist/automations/notifications/rules/discussion.d.ts +7 -0
  49. package/dist/automations/notifications/rules/discussion.js +678 -0
  50. package/dist/automations/notifications/rules/discussion.js.map +1 -0
  51. package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
  52. package/dist/automations/notifications/rules/entitlements.js +276 -0
  53. package/dist/automations/notifications/rules/entitlements.js.map +1 -0
  54. package/dist/automations/notifications/rules/events.d.ts +9 -0
  55. package/dist/automations/notifications/rules/events.js +27 -0
  56. package/dist/automations/notifications/rules/events.js.map +1 -0
  57. package/dist/automations/notifications/rules/index.d.ts +6 -0
  58. package/dist/automations/notifications/rules/index.js +15 -0
  59. package/dist/automations/notifications/rules/index.js.map +1 -0
  60. package/dist/automations/notifications/rules/organizations.d.ts +6 -0
  61. package/dist/automations/notifications/rules/organizations.js +35 -0
  62. package/dist/automations/notifications/rules/organizations.js.map +1 -0
  63. package/dist/automations/notifications/rules/recipients.d.ts +32 -0
  64. package/dist/automations/notifications/rules/recipients.js +98 -0
  65. package/dist/automations/notifications/rules/recipients.js.map +1 -0
  66. package/dist/automations/notifications/service.d.ts +29 -0
  67. package/dist/automations/notifications/service.js +45 -0
  68. package/dist/automations/notifications/service.js.map +1 -0
  69. package/dist/automations/notifications/types.d.ts +7 -0
  70. package/dist/automations/notifications/types.js +560 -0
  71. package/dist/automations/notifications/types.js.map +1 -0
  72. package/dist/automations/ports.d.ts +134 -21
  73. package/dist/automations/service.d.ts +30 -30
  74. package/dist/automations/service.js +111 -144
  75. package/dist/automations/service.js.map +1 -1
  76. package/dist/content/event-sight.d.ts +11 -0
  77. package/dist/content/event-sight.js +11 -0
  78. package/dist/content/event-sight.js.map +1 -0
  79. package/dist/content/events.d.ts +437 -46
  80. package/dist/content/events.js +45 -1
  81. package/dist/content/events.js.map +1 -1
  82. package/dist/content/index.d.ts +8 -4
  83. package/dist/content/index.js +5 -1
  84. package/dist/content/jobs.d.ts +16 -0
  85. package/dist/content/jobs.js +37 -0
  86. package/dist/content/jobs.js.map +1 -0
  87. package/dist/content/model.d.ts +2 -2
  88. package/dist/content/notification-categories.d.ts +37 -0
  89. package/dist/content/notification-categories.js +73 -0
  90. package/dist/content/notification-categories.js.map +1 -0
  91. package/dist/content/ports.d.ts +132 -68
  92. package/dist/content/service.d.ts +100 -68
  93. package/dist/content/service.js +703 -146
  94. package/dist/content/service.js.map +1 -1
  95. package/dist/content/titles.d.ts +25 -0
  96. package/dist/content/titles.js +82 -0
  97. package/dist/content/titles.js.map +1 -0
  98. package/dist/content/types.d.ts +2 -2
  99. package/dist/discussion/authors.d.ts +4 -2
  100. package/dist/discussion/authors.js +9 -5
  101. package/dist/discussion/authors.js.map +1 -1
  102. package/dist/discussion/chats.d.ts +63 -39
  103. package/dist/discussion/chats.js +455 -190
  104. package/dist/discussion/chats.js.map +1 -1
  105. package/dist/discussion/digests.d.ts +24 -0
  106. package/dist/discussion/digests.js +104 -0
  107. package/dist/discussion/digests.js.map +1 -0
  108. package/dist/discussion/events.d.ts +227 -23
  109. package/dist/discussion/events.js +114 -19
  110. package/dist/discussion/events.js.map +1 -1
  111. package/dist/discussion/index.d.ts +12 -4
  112. package/dist/discussion/index.js +9 -2
  113. package/dist/discussion/jobs.d.ts +16 -0
  114. package/dist/discussion/jobs.js +40 -0
  115. package/dist/discussion/jobs.js.map +1 -0
  116. package/dist/discussion/live.d.ts +106 -0
  117. package/dist/discussion/live.js +642 -0
  118. package/dist/discussion/live.js.map +1 -0
  119. package/dist/discussion/mentions.d.ts +11 -0
  120. package/dist/discussion/mentions.js +35 -0
  121. package/dist/discussion/mentions.js.map +1 -0
  122. package/dist/discussion/ports.d.ts +279 -114
  123. package/dist/discussion/ports.js.map +1 -1
  124. package/dist/discussion/posts.d.ts +60 -44
  125. package/dist/discussion/posts.js +453 -287
  126. package/dist/discussion/posts.js.map +1 -1
  127. package/dist/discussion/rules.d.ts +18 -0
  128. package/dist/discussion/rules.js +22 -0
  129. package/dist/discussion/rules.js.map +1 -0
  130. package/dist/discussion/service.d.ts +50 -35
  131. package/dist/discussion/service.js +333 -205
  132. package/dist/discussion/service.js.map +1 -1
  133. package/dist/discussion/subscriptions.d.ts +37 -0
  134. package/dist/discussion/subscriptions.js +115 -0
  135. package/dist/discussion/subscriptions.js.map +1 -0
  136. package/dist/discussion/titles.d.ts +28 -0
  137. package/dist/discussion/titles.js +98 -0
  138. package/dist/discussion/titles.js.map +1 -0
  139. package/dist/entitlements/events.d.ts +20 -20
  140. package/dist/entitlements/index.d.ts +5 -4
  141. package/dist/entitlements/index.js +3 -2
  142. package/dist/entitlements/jobs.d.ts +12 -0
  143. package/dist/entitlements/jobs.js +18 -0
  144. package/dist/entitlements/jobs.js.map +1 -0
  145. package/dist/entitlements/model.d.ts +2 -2
  146. package/dist/entitlements/ports.d.ts +68 -38
  147. package/dist/entitlements/rules.d.ts +3 -6
  148. package/dist/entitlements/rules.js +5 -10
  149. package/dist/entitlements/rules.js.map +1 -1
  150. package/dist/entitlements/service.d.ts +43 -28
  151. package/dist/entitlements/service.js +387 -160
  152. package/dist/entitlements/service.js.map +1 -1
  153. package/dist/identity/devices.d.ts +30 -0
  154. package/dist/identity/devices.js +109 -0
  155. package/dist/identity/devices.js.map +1 -0
  156. package/dist/identity/index.d.ts +6 -5
  157. package/dist/identity/index.js +4 -3
  158. package/dist/identity/model.d.ts +2 -2
  159. package/dist/identity/notification-settings.d.ts +28 -0
  160. package/dist/identity/notification-settings.js +114 -0
  161. package/dist/identity/notification-settings.js.map +1 -0
  162. package/dist/identity/ports.d.ts +80 -56
  163. package/dist/identity/service.d.ts +8 -7
  164. package/dist/identity/service.js +17 -13
  165. package/dist/identity/service.js.map +1 -1
  166. package/dist/integrations/ports.d.ts +9 -8
  167. package/dist/integrations/service.d.ts +9 -8
  168. package/dist/integrations/service.js +33 -16
  169. package/dist/integrations/service.js.map +1 -1
  170. package/dist/organizations/default-roles.d.ts +22 -0
  171. package/dist/organizations/default-roles.js +89 -0
  172. package/dist/organizations/default-roles.js.map +1 -0
  173. package/dist/organizations/effective-permissions.d.ts +45 -0
  174. package/dist/organizations/effective-permissions.js +113 -0
  175. package/dist/organizations/effective-permissions.js.map +1 -0
  176. package/dist/organizations/events.d.ts +320 -181
  177. package/dist/organizations/events.js +90 -18
  178. package/dist/organizations/events.js.map +1 -1
  179. package/dist/organizations/index.d.ts +15 -10
  180. package/dist/organizations/index.js +8 -3
  181. package/dist/organizations/members.d.ts +66 -8
  182. package/dist/organizations/members.js.map +1 -1
  183. package/dist/organizations/notification-catalog.d.ts +31 -0
  184. package/dist/organizations/notification-catalog.js +32 -0
  185. package/dist/organizations/notification-catalog.js.map +1 -0
  186. package/dist/organizations/notification-categories.d.ts +34 -0
  187. package/dist/organizations/notification-categories.js +66 -0
  188. package/dist/organizations/notification-categories.js.map +1 -0
  189. package/dist/organizations/org-roles.d.ts +33 -0
  190. package/dist/organizations/org-roles.js +49 -0
  191. package/dist/organizations/org-roles.js.map +1 -0
  192. package/dist/organizations/permissions.d.ts +311 -0
  193. package/dist/organizations/permissions.js +327 -0
  194. package/dist/organizations/permissions.js.map +1 -0
  195. package/dist/organizations/ports.d.ts +187 -65
  196. package/dist/organizations/service.d.ts +104 -35
  197. package/dist/organizations/service.js +1247 -323
  198. package/dist/organizations/service.js.map +1 -1
  199. package/dist/organizations/testing.d.ts +18 -0
  200. package/dist/organizations/testing.js +37 -0
  201. package/dist/organizations/testing.js.map +1 -0
  202. package/dist/organizations/types.d.ts +5 -16
  203. package/dist/progress/index.d.ts +2 -2
  204. package/dist/progress/ports.d.ts +11 -3
  205. package/dist/progress/service.d.ts +6 -5
  206. package/dist/progress/service.js +23 -8
  207. package/dist/progress/service.js.map +1 -1
  208. package/dist/reporting/admin/index.d.ts +6 -4
  209. package/dist/reporting/admin/model.d.ts +36 -3
  210. package/dist/reporting/admin/ports.d.ts +39 -14
  211. package/dist/reporting/admin/service.d.ts +32 -11
  212. package/dist/reporting/admin/service.js +102 -18
  213. package/dist/reporting/admin/service.js.map +1 -1
  214. package/dist/reporting/events/index.d.ts +27 -0
  215. package/dist/reporting/events/index.js +20 -0
  216. package/dist/reporting/events/index.js.map +1 -0
  217. package/dist/reporting/member/calendar.d.ts +11 -0
  218. package/dist/reporting/member/calendar.js +75 -0
  219. package/dist/reporting/member/calendar.js.map +1 -0
  220. package/dist/reporting/member/index.d.ts +3 -2
  221. package/dist/reporting/member/model.d.ts +80 -26
  222. package/dist/reporting/member/ports.d.ts +50 -28
  223. package/dist/reporting/member/service.d.ts +81 -35
  224. package/dist/reporting/member/service.js +438 -145
  225. package/dist/reporting/member/service.js.map +1 -1
  226. package/dist/shared/actor.d.ts +22 -3
  227. package/dist/shared/errors.d.ts +15 -2
  228. package/dist/shared/errors.js +21 -2
  229. package/dist/shared/errors.js.map +1 -1
  230. package/dist/shared/id.d.ts +7 -0
  231. package/dist/shared/id.js +8 -1
  232. package/dist/shared/id.js.map +1 -1
  233. package/dist/shared/logger.d.ts +2 -1
  234. package/dist/shared/logger.js +10 -1
  235. package/dist/shared/logger.js.map +1 -1
  236. package/dist/shared/mailer.d.ts +11 -1
  237. package/dist/shared/mailer.js +32 -5
  238. package/dist/shared/mailer.js.map +1 -1
  239. package/dist/shared/notification-categories.d.ts +16 -0
  240. package/dist/shared/notification-categories.js +94 -0
  241. package/dist/shared/notification-categories.js.map +1 -0
  242. package/dist/shared/notification-settings.d.ts +14 -0
  243. package/dist/shared/notification-settings.js +61 -0
  244. package/dist/shared/notification-settings.js.map +1 -0
  245. package/dist/shared/ports.d.ts +68 -2
  246. package/dist/shared/settings.d.ts +2 -1
  247. package/dist/shared/settings.js +1 -0
  248. package/dist/shared/settings.js.map +1 -1
  249. package/dist/types/automations.d.ts +8 -2
  250. package/dist/types/content.d.ts +2 -2
  251. package/dist/types/discussion.d.ts +4 -5
  252. package/dist/types/email-templates.d.ts +119 -6
  253. package/dist/types/entitlements.d.ts +2 -2
  254. package/dist/types/identity.d.ts +14 -17
  255. package/dist/types/index.d.ts +13 -12
  256. package/dist/types/index.js +2 -1
  257. package/dist/types/notifications.d.ts +157 -0
  258. package/dist/types/notifications.js +1 -0
  259. package/dist/types/organizations.d.ts +3 -2
  260. package/dist/types/ports.d.ts +8 -1
  261. package/dist/types/ports.js +7 -1
  262. package/dist/types/ports.js.map +1 -0
  263. package/dist/types/schemas/assets.d.ts +5 -5
  264. package/dist/types/schemas/assets.js +4 -3
  265. package/dist/types/schemas/assets.js.map +1 -1
  266. package/dist/types/schemas/automations.d.ts +1 -10
  267. package/dist/types/schemas/automations.js +1 -3
  268. package/dist/types/schemas/automations.js.map +1 -1
  269. package/dist/types/schemas/content.d.ts +596 -11
  270. package/dist/types/schemas/content.js +225 -9
  271. package/dist/types/schemas/content.js.map +1 -1
  272. package/dist/types/schemas/discussion.d.ts +362 -66
  273. package/dist/types/schemas/discussion.js +123 -15
  274. package/dist/types/schemas/discussion.js.map +1 -1
  275. package/dist/types/schemas/entitlements.d.ts +14 -1
  276. package/dist/types/schemas/entitlements.js +11 -4
  277. package/dist/types/schemas/entitlements.js.map +1 -1
  278. package/dist/types/schemas/identity.d.ts +1 -85
  279. package/dist/types/schemas/identity.js +1 -42
  280. package/dist/types/schemas/identity.js.map +1 -1
  281. package/dist/types/schemas/index.d.ts +8 -7
  282. package/dist/types/schemas/index.js +8 -7
  283. package/dist/types/schemas/notifications.d.ts +96 -0
  284. package/dist/types/schemas/notifications.js +57 -0
  285. package/dist/types/schemas/notifications.js.map +1 -0
  286. package/dist/types/schemas/organizations.d.ts +96 -60
  287. package/dist/types/schemas/organizations.js +78 -27
  288. package/dist/types/schemas/organizations.js.map +1 -1
  289. package/dist/types/schemas/progress.d.ts +0 -1
  290. package/dist/types/schemas/progress.js +0 -1
  291. package/dist/types/schemas/progress.js.map +1 -1
  292. package/package.json +1 -1
  293. package/dist/automations/community-notifications.d.ts +0 -16
  294. package/dist/automations/community-notifications.js +0 -84
  295. package/dist/automations/community-notifications.js.map +0 -1
  296. package/dist/automations/seeds.d.ts +0 -17
  297. package/dist/automations/seeds.js +0 -235
  298. package/dist/automations/seeds.js.map +0 -1
  299. package/dist/identity/notifications.d.ts +0 -27
  300. package/dist/identity/notifications.js +0 -101
  301. package/dist/identity/notifications.js.map +0 -1
  302. package/dist/organizations/access-scopes.d.ts +0 -19
  303. package/dist/organizations/access-scopes.js +0 -89
  304. package/dist/organizations/access-scopes.js.map +0 -1
  305. package/dist/organizations/roles.d.ts +0 -19
  306. package/dist/organizations/roles.js +0 -82
  307. package/dist/organizations/roles.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"chats.js","names":[],"sources":["../../src/discussion/chats.ts"],"sourcesContent":["import { genId } from '../shared/id.js';\nimport {\n ConflictError,\n ForbiddenError,\n InvalidInputError,\n NotFoundError,\n} from '../shared/errors.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { isStaffRole } from '../organizations/index.js';\nimport type { Role } from '../organizations/index.js';\nimport type { CommunityReader, DmSettings } from '../content/index.js';\nimport type { CommunityAccessReader } from '../entitlements/index.js';\nimport type { AssetFiles, FileUrls, MemberImageReader } from '../assets/index.js';\nimport type {\n Chat,\n ChatUser,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Page,\n ReactionEmoji,\n} from '../types/index.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { NewDiscussionEvent } from './events.js';\nimport { discussionEvents } from './events.js';\nimport { authorView } from './authors.js';\nimport type {\n AuthorProfileReader,\n AuthorRoleReader,\n ChatListItem,\n ChatRepository,\n ChatService,\n ChatUnitOfWork,\n MessageQueue,\n MessageQueueEntry,\n MessageReactions,\n PageQuery,\n SendMessageInput,\n StartDirectInput,\n StartGroupInput,\n} from './ports.js';\nimport { MODERATION_QUEUE_LIMIT } from './ports.js';\n\nexport type ChatServiceParams = {\n repo: ChatRepository;\n access: CommunityAccessReader;\n content: CommunityReader;\n authors: AuthorRoleReader;\n profiles: AuthorProfileReader;\n images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n fileUrls: FileUrls;\n uow: ChatUnitOfWork;\n logger?: Logger;\n};\n\nexport class ChatServiceImpl implements ChatService {\n private readonly repo: ChatRepository;\n private readonly access: CommunityAccessReader;\n private readonly content: CommunityReader;\n private readonly authors: AuthorRoleReader;\n private readonly profiles: AuthorProfileReader;\n private readonly images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n private readonly fileUrls: FileUrls;\n private readonly uow: ChatUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: ChatServiceParams) {\n this.repo = params.repo;\n this.access = params.access;\n this.content = params.content;\n this.authors = params.authors;\n this.profiles = params.profiles;\n this.images = params.images;\n this.fileUrls = params.fileUrls;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n async startDirect(orgId: string, actor: Actor, input: StartDirectInput): Promise<Chat> {\n if (actor.id === input.orgUserId) {\n throw new InvalidInputError('You cannot message yourself');\n }\n if (input.body === null && input.assetIds.length > 0) {\n throw new InvalidInputError('An attachment needs a message');\n }\n const settings = await this.requireSettings(orgId, input.communityId);\n await this.requireActor(orgId, actor, input.communityId);\n const role = await this.requirePerson(orgId, input.communityId, input.orgUserId);\n this.assertMayMessage(settings, actor, role);\n const first: SendMessageInput | null =\n input.body === null ? null : { body: input.body, replyToId: null, assetIds: input.assetIds };\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n await chats.lockDirectPair(orgId, input.communityId, actor.id, input.orgUserId);\n const existing = await chats.findDirect(orgId, input.communityId, actor.id, input.orgUserId);\n if (existing) {\n const users = await chats.participants(orgId, existing.id);\n const rejoin = users.some((user) => user.orgUserId === actor.id && user.leftAt !== null);\n const present = users.map((user) =>\n user.orgUserId === actor.id ? { ...user, leftAt: null } : user,\n );\n if (first) {\n await this.assertMaySend(chats, orgId, actor, existing, present, settings, first);\n }\n if (rejoin) {\n await chats.join(\n orgId,\n this.memberRow(orgId, existing.communityId, existing.id, actor.id, new Date()),\n );\n }\n const written = first\n ? await this.writeMessage(chats, orgId, actor, existing, first)\n : null;\n const chat = written?.chat ?? existing;\n const events: NewDiscussionEvent[] = [];\n if (rejoin || written?.opened) {\n events.push(this.updatedEvent(orgId, actor, chat, present));\n }\n if (written) {\n events.push(this.sentEvent(orgId, actor, written.chat, written.message));\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n return { chat, created: false };\n }\n await this.requireOwnedImages(orgId, actor.id, input.assetIds);\n if (await chats.isBlocked(orgId, input.communityId, actor.id, input.orgUserId)) {\n throw new ForbiddenError('You cannot message this person');\n }\n const request =\n settings.firstMessageIsRequest && !isStaffRole(actor.role) && !isStaffRole(role);\n if (request && !first) {\n throw new InvalidInputError('A message request needs a message');\n }\n const now = new Date();\n const chat = await chats.insertChat(orgId, {\n orgId,\n id: genId('chat'),\n communityId: input.communityId,\n kind: 'direct',\n status: request ? 'pending' : 'open',\n createdBy: actor.id,\n lockedAt: null,\n lockedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n const users = [actor.id, input.orgUserId].map((orgUserId) =>\n this.memberRow(orgId, chat.communityId, chat.id, orgUserId, now),\n );\n for (const user of users) {\n await chats.join(orgId, user);\n }\n const events: NewDiscussionEvent[] = [this.createdEvent(orgId, actor, chat, users)];\n if (first) {\n const written = await this.writeMessage(chats, orgId, actor, chat, first);\n events.push(this.sentEvent(orgId, actor, written.chat, written.message));\n }\n await outbox.append(events);\n return { chat, created: true };\n });\n if (saved.created) {\n this.logger.info('direct chat started', {\n orgId,\n communityId: input.communityId,\n chatId: saved.chat.id,\n status: saved.chat.status,\n actorId: actor.id,\n });\n } else {\n this.logger.debug('direct chat already exists', {\n orgId,\n communityId: input.communityId,\n chatId: saved.chat.id,\n });\n }\n return saved.chat;\n }\n\n async startGroup(orgId: string, actor: Actor, input: StartGroupInput): Promise<Chat> {\n const settings = await this.requireSettings(orgId, input.communityId);\n this.assertGroup(settings);\n await this.requireActor(orgId, actor, input.communityId);\n const others = [...new Set(input.orgUserIds.filter((id) => id !== actor.id))].sort();\n if (others.length < 2) {\n throw new InvalidInputError('A group chat needs three people');\n }\n if (others.length + 1 > settings.groupSize) {\n throw new ConflictError('This group is full');\n }\n for (const orgUserId of others) {\n const role = await this.requirePerson(orgId, input.communityId, orgUserId);\n this.assertMayMessage(settings, actor, role);\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n if (await chats.anyBlockedAmong(orgId, input.communityId, [actor.id, ...others])) {\n throw new ForbiddenError('You cannot message this person');\n }\n const now = new Date();\n const chat = await chats.insertChat(orgId, {\n orgId,\n id: genId('chat'),\n communityId: input.communityId,\n kind: 'group',\n status: 'open',\n createdBy: actor.id,\n lockedAt: null,\n lockedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n const ids = [actor.id, ...others];\n const users = ids.map((orgUserId) =>\n this.memberRow(orgId, chat.communityId, chat.id, orgUserId, now),\n );\n for (const user of users) {\n await chats.join(orgId, user);\n }\n const events: NewDiscussionEvent[] = [\n this.createdEvent(orgId, actor, chat, users),\n ...others.map((orgUserId) => this.addedEvent(orgId, actor, chat, orgUserId)),\n ];\n await outbox.append(events);\n return chat;\n });\n this.logger.info('group chat started', {\n orgId,\n communityId: input.communityId,\n chatId: saved.id,\n actorId: actor.id,\n });\n return saved;\n }\n\n async addParticipant(\n orgId: string,\n actor: Actor,\n chatId: string,\n orgUserId: string,\n ): Promise<Chat> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.kind !== 'group') {\n throw new ConflictError('This chat is not a group');\n }\n const settings = await this.requireSettings(orgId, chat.communityId);\n this.assertGroup(settings);\n const role = await this.requirePerson(orgId, chat.communityId, orgUserId);\n this.assertMayMessage(settings, actor, role);\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n this.assertActive(users, actor.id);\n const existing = users.find((user) => user.orgUserId === orgUserId);\n if (existing && existing.leftAt === null) {\n throw new ConflictError('That person is already in this chat');\n }\n const active = users.filter((user) => user.leftAt === null);\n if (active.length + 1 > settings.groupSize) {\n throw new ConflictError('This group is full');\n }\n const blocked = await chats.blockedWith(\n orgId,\n current.communityId,\n orgUserId,\n active.map((user) => user.orgUserId),\n );\n if (blocked.length > 0) {\n throw new ForbiddenError('You cannot message this person');\n }\n const now = new Date();\n await chats.join(\n orgId,\n this.memberRow(orgId, current.communityId, current.id, orgUserId, now),\n );\n await outbox.append([this.addedEvent(orgId, actor, current, orgUserId)]);\n return current;\n });\n this.logger.info('chat participant added', { orgId, chatId, orgUserId, actorId: actor.id });\n return saved;\n }\n\n async leave(orgId: string, actor: Actor, chatId: string): Promise<Chat> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n this.assertActive(users, actor.id);\n const now = new Date();\n await chats.markLeft(orgId, chatId, actor.id, now);\n const remaining = users.map((user) =>\n user.orgUserId === actor.id ? { ...user, leftAt: now } : user,\n );\n await outbox.append([this.updatedEvent(orgId, actor, current, remaining)]);\n return current;\n });\n this.logger.info('chat left', { orgId, chatId, actorId: actor.id });\n return saved;\n }\n\n async accept(orgId: string, actor: Actor, chatId: string): Promise<Chat> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.createdBy === actor.id) {\n throw new ForbiddenError('You cannot accept this request');\n }\n if (chat.kind !== 'direct' || chat.status !== 'pending') {\n throw new ConflictError('This chat is not a request');\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n if (current.kind !== 'direct' || current.status !== 'pending') {\n throw new ConflictError('This chat is not a request');\n }\n const next = await this.mustUpdate(chats, orgId, chatId, { status: 'open' });\n const users = await chats.participants(orgId, chatId);\n await outbox.append([this.updatedEvent(orgId, actor, next, users)]);\n return next;\n });\n this.logger.info('chat accepted', { orgId, chatId, actorId: actor.id });\n return saved;\n }\n\n async send(\n orgId: string,\n actor: Actor,\n chatId: string,\n input: SendMessageInput,\n ): Promise<MessageView> {\n const preview = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, preview.communityId);\n const settings = await this.requireSettings(orgId, preview.communityId);\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const chat = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n await this.assertMaySend(chats, orgId, actor, chat, users, settings, input);\n const written = await this.writeMessage(chats, orgId, actor, chat, input);\n const events: NewDiscussionEvent[] = [];\n if (written.opened) {\n events.push(this.updatedEvent(orgId, actor, written.chat, users));\n }\n events.push(this.sentEvent(orgId, actor, written.chat, written.message));\n await outbox.append(events);\n return written.message;\n });\n this.logger.info('message sent', { orgId, chatId, messageId: saved.id, actorId: actor.id });\n return this.viewOf(orgId, actor, saved);\n }\n\n async editMessage(\n orgId: string,\n actor: Actor,\n messageId: string,\n body: string,\n ): Promise<MessageView> {\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireWritableChat(orgId, actor, message.chatId);\n if (message.orgUserId !== actor.id) {\n throw new ForbiddenError('You can change only your own message');\n }\n if (message.deletedAt) {\n throw new ConflictError('This message was deleted');\n }\n if (message.body === body) {\n return this.viewOf(orgId, actor, message);\n }\n const saved = await this.repo.updateMessage(orgId, messageId, { body, editedAt: new Date() });\n if (!saved) {\n throw new NotFoundError('Message', messageId);\n }\n this.logger.info('message edited', { orgId, messageId, actorId: actor.id });\n return this.viewOf(orgId, actor, saved);\n }\n\n async deleteMessage(orgId: string, actor: Actor, messageId: string): Promise<MessageView> {\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(orgId, actor, message.communityId);\n if (!isStaffRole(actor.role)) {\n if (message.orgUserId !== actor.id) {\n throw new ForbiddenError('You cannot delete this message');\n }\n await this.requireWritableChat(orgId, actor, message.chatId);\n }\n if (message.deletedAt) {\n throw new ConflictError('This message was deleted');\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const next = await chats.updateMessage(orgId, messageId, { deletedAt: now });\n if (!next) {\n throw new NotFoundError('Message', messageId);\n }\n await chats.resolveFlags(orgId, messageId, {\n resolvedAt: now,\n resolvedBy: actor.id,\n resolution: 'deleted',\n });\n await outbox.append([\n discussionEvents.messageDeleted.make({\n orgId,\n data: next,\n metadata: this.meta(actor),\n }),\n ]);\n return next;\n });\n this.logger.info('message deleted', { orgId, messageId, actorId: actor.id });\n return this.viewOf(orgId, actor, saved);\n }\n\n async setReaction(\n orgId: string,\n actor: Actor,\n messageId: string,\n emoji: ReactionEmoji | null,\n ): Promise<MessageReactions> {\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(orgId, actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n await this.repo.setReaction(orgId, message.communityId, messageId, actor.id, emoji);\n this.logger.debug('message reaction set', { orgId, messageId });\n const counts = await this.repo.reactionsOf(orgId, [messageId], actor.id);\n return counts[messageId] ?? { reactions: {} };\n }\n\n async markRead(orgId: string, actor: Actor, chatId: string, messageId: string): Promise<void> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (message.chatId !== chatId) {\n throw new NotFoundError('Message', messageId);\n }\n await this.repo.setRead(orgId, chatId, actor.id, messageId);\n this.logger.debug('chat marked read', { orgId, chatId, messageId });\n }\n\n async mute(orgId: string, actor: Actor, chatId: string, until?: Date): Promise<void> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n await this.repo.setMuted(orgId, chatId, actor.id, true, until ?? null);\n this.logger.debug('chat muted', { orgId, chatId });\n }\n\n async unmute(orgId: string, actor: Actor, chatId: string): Promise<void> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n await this.repo.setMuted(orgId, chatId, actor.id, false, null);\n this.logger.debug('chat unmuted', { orgId, chatId });\n }\n\n async block(orgId: string, actor: Actor, communityId: string, orgUserId: string): Promise<void> {\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(orgId, actor, communityId);\n await this.requirePerson(orgId, communityId, orgUserId);\n await this.uow.run(async ({ chats }) => {\n await chats.lockDirectPair(orgId, communityId, actor.id, orgUserId);\n await chats.block(orgId, communityId, actor.id, orgUserId);\n });\n this.logger.info('user blocked', { orgId, communityId, actorId: actor.id });\n }\n\n async unblock(\n orgId: string,\n actor: Actor,\n communityId: string,\n orgUserId: string,\n ): Promise<void> {\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(orgId, actor, communityId);\n await this.uow.run(async ({ chats }) => {\n await chats.lockDirectPair(orgId, communityId, actor.id, orgUserId);\n await chats.unblock(orgId, communityId, actor.id, orgUserId);\n });\n this.logger.info('user unblocked', { orgId, communityId, actorId: actor.id });\n }\n\n async flagMessage(\n orgId: string,\n actor: Actor,\n messageId: string,\n reason: string,\n ): Promise<MessageFlag> {\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(orgId, actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const flag = await chats.insertFlag(orgId, {\n orgId,\n id: genId('messageFlag'),\n communityId: message.communityId,\n messageId,\n orgUserId: actor.id,\n reason,\n resolvedAt: null,\n resolution: null,\n resolvedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n if (!flag) {\n const existing = await chats.findFlag(orgId, messageId, actor.id);\n if (!existing) {\n throw new NotFoundError('Message', messageId);\n }\n return existing;\n }\n await outbox.append([\n discussionEvents.messageReported.make({\n orgId,\n data: flag,\n metadata: this.meta(actor),\n }),\n ]);\n return flag;\n });\n this.logger.info('message flagged', { orgId, messageId, actorId: actor.id });\n return saved;\n }\n\n async resolveMessageFlags(\n orgId: string,\n actor: Actor,\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId?: string,\n ): Promise<void> {\n if (!isStaffRole(actor.role)) {\n throw new ForbiddenError('You cannot work this queue');\n }\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (communityId !== undefined && message.communityId !== communityId) {\n throw new NotFoundError('Message', messageId);\n }\n await this.requireActor(orgId, actor, message.communityId);\n await this.repo.resolveFlags(orgId, messageId, {\n resolvedAt: new Date(),\n resolvedBy: actor.id,\n resolution,\n });\n this.logger.info('message flags resolved', {\n orgId,\n communityId: message.communityId,\n messageId,\n resolution,\n actorId: actor.id,\n });\n }\n\n async lock(orgId: string, actor: Actor, chatId: string, communityId?: string): Promise<Chat> {\n return this.setLock(orgId, actor, chatId, true, communityId);\n }\n\n async unlock(orgId: string, actor: Actor, chatId: string, communityId?: string): Promise<Chat> {\n return this.setLock(orgId, actor, chatId, false, communityId);\n }\n\n async listChats(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<ChatListItem>> {\n await this.requireSettings(orgId, communityId);\n await this.requireActor(orgId, actor, communityId);\n const now = new Date();\n const page = await this.repo.listForUser(orgId, communityId, actor.id, query);\n const items = page.rows.map((item) => ({\n ...item,\n ...(item.muted && item.mutedUntil !== null && item.mutedUntil <= now\n ? { muted: false, mutedUntil: null }\n : {}),\n lastMessage:\n item.lastMessage && item.lastMessage.deletedAt !== null && !isStaffRole(actor.role)\n ? { ...item.lastMessage, body: '' }\n : item.lastMessage,\n }));\n this.logger.debug('chats listed', { orgId, communityId, count: items.length });\n return { ...page, rows: items };\n }\n\n async messages(\n orgId: string,\n actor: Actor,\n chatId: string,\n query: { page: number; pageSize: number },\n ): Promise<Page<MessageView>> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const page = await this.repo.listMessages(orgId, chatId, query);\n this.logger.debug('chat messages read', { orgId, chatId });\n return {\n ...page,\n rows: await this.withSenders(orgId, actor, page.rows),\n };\n }\n\n async attachmentFile(\n orgId: string,\n actor: Actor,\n chatId: string,\n messageId: string,\n assetId: string,\n ): Promise<string | null> {\n const chat = await this.repo.findChat(orgId, chatId);\n const message = await this.repo.findMessage(orgId, messageId);\n if (!chat || !message || message.chatId !== chatId) {\n return null;\n }\n const attached = (await this.repo.attachmentsOf(orgId, [messageId]))[messageId] ?? [];\n if (!attached.includes(assetId)) {\n return null;\n }\n if (!(await this.access.isCommunityMember(orgId, actor, chat.communityId))) {\n return null;\n }\n const users = await this.repo.participants(orgId, chatId);\n const inChat = users.some((user) => user.orgUserId === actor.id && user.leftAt === null);\n const readable = inChat\n ? message.deletedAt === null || isStaffRole(actor.role)\n : isStaffRole(actor.role) &&\n (await this.repo.listOpenFlags(orgId, chat.communityId)).some(\n (flag) => flag.messageId === messageId,\n );\n if (!readable) {\n return null;\n }\n return this.images.signFile(orgId, assetId);\n }\n\n async messageQueue(orgId: string, actor: Actor, communityId: string): Promise<MessageQueue> {\n if (!isStaffRole(actor.role)) {\n throw new ForbiddenError('You cannot work this queue');\n }\n await this.requireActor(orgId, actor, communityId);\n await this.requireSettings(orgId, communityId);\n const flags = await this.repo.listOpenFlags(orgId, communityId, MODERATION_QUEUE_LIMIT);\n const messages = await this.repo.messagesByIds(orgId, [\n ...new Set(flags.map((flag) => flag.messageId)),\n ]);\n const summaries = await this.repo.chatSummaries(orgId, [\n ...new Set(messages.map((message) => message.chatId)),\n ]);\n const byChat = new Map(summaries.map((summary) => [summary.chat.id, summary]));\n const views = await this.withSenders(orgId, actor, messages);\n const entries: MessageQueueEntry[] = views.flatMap((message) => {\n const summary = byChat.get(message.chatId);\n if (!summary) {\n return [];\n }\n return [\n {\n message,\n flags: flags.filter((flag) => flag.messageId === message.id),\n chat: summary.chat,\n participantIds: summary.participantIds,\n },\n ];\n });\n this.logger.debug('message queue read', { orgId, communityId, count: entries.length });\n return { entries };\n }\n\n async chatSummary(\n orgId: string,\n chatId: string,\n ): Promise<{ chat: Chat; participants: ChatUser[] } | null> {\n const chat = await this.repo.findChat(orgId, chatId);\n if (!chat) {\n return null;\n }\n const participants = await this.repo.participants(orgId, chatId);\n return { chat, participants };\n }\n\n async titlesFor(orgId: string, actor: Actor, chatIds: string[]): Promise<Record<string, string>> {\n if (chatIds.length === 0) {\n return {};\n }\n const summaries = (await this.repo.chatSummaries(orgId, [...new Set(chatIds)])).filter(\n (summary) => summary.participantIds.includes(actor.id),\n );\n const others = [\n ...new Set(\n summaries.flatMap((summary) => summary.participantIds.filter((id) => id !== actor.id)),\n ),\n ];\n const records = others.length > 0 ? await this.profiles.authorsOf(orgId, others) : {};\n const nameOf = (id: string): string => {\n const record = records[id];\n return (record && authorView(orgId, record, null).displayName) || 'A member';\n };\n return Object.fromEntries(\n summaries.flatMap((summary) => {\n const names = summary.participantIds.filter((id) => id !== actor.id).map(nameOf);\n if (names.length > 0) {\n return [[summary.chat.id, names.join(', ')]];\n }\n return summary.chat.kind === 'group' ? [[summary.chat.id, 'Group chat']] : [];\n }),\n );\n }\n\n async latestMessageBody(orgId: string, chatId: string): Promise<string | null> {\n const page = await this.repo.listMessages(orgId, chatId, { page: 1, pageSize: 1 });\n return page.rows[0]?.body ?? null;\n }\n\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean> {\n return this.repo.isBlocked(orgId, communityId, a, b);\n }\n\n private async setLock(\n orgId: string,\n actor: Actor,\n chatId: string,\n locked: boolean,\n communityId?: string,\n ): Promise<Chat> {\n if (!isStaffRole(actor.role)) {\n throw new ForbiddenError('You cannot lock this chat');\n }\n const chat = await this.mustChat(this.repo, orgId, chatId);\n if (communityId !== undefined && chat.communityId !== communityId) {\n throw new NotFoundError('Chat', chatId);\n }\n await this.requireActor(orgId, actor, chat.communityId);\n if (Boolean(chat.lockedAt) === locked) {\n return chat;\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const next = await this.mustUpdate(chats, orgId, chatId, {\n lockedAt: locked ? now : null,\n lockedBy: locked ? actor.id : null,\n });\n if (locked) {\n await chats.resolveOpenFlagsForChat(orgId, chatId, {\n resolvedAt: now,\n resolvedBy: actor.id,\n resolution: 'locked',\n });\n }\n const users = await chats.participants(orgId, chatId);\n await outbox.append([this.updatedEvent(orgId, actor, next, users)]);\n return next;\n });\n this.logger.info(locked ? 'chat locked' : 'chat unlocked', {\n orgId,\n chatId,\n actorId: actor.id,\n });\n return saved;\n }\n\n private async requireSettings(orgId: string, communityId: string): Promise<DmSettings> {\n const settings = await this.content.getDmSettings(orgId, communityId);\n if (!settings) {\n throw new NotFoundError('Community', communityId);\n }\n return settings;\n }\n\n private async requireActor(orgId: string, actor: Actor, communityId: string): Promise<void> {\n if (!(await this.access.isCommunityMember(orgId, actor, communityId))) {\n throw new ForbiddenError('You are not a member of this community');\n }\n }\n\n private async requirePerson(\n orgId: string,\n communityId: string,\n orgUserId: string,\n ): Promise<Role> {\n const role = await this.personRole(orgId, communityId, orgUserId);\n if (!role) {\n throw new ForbiddenError('That person is not a member of this community');\n }\n return role;\n }\n\n private async personRole(\n orgId: string,\n communityId: string,\n orgUserId: string,\n ): Promise<Role | null> {\n const role = await this.authors.roleOf(orgId, orgUserId);\n if (!role) {\n return null;\n }\n const member = await this.access.isCommunityMember(orgId, { id: orgUserId, role }, communityId);\n return member ? role : null;\n }\n\n private async personRoles(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<Record<string, Role>> {\n const roles = await this.authors.rolesOf(orgId, orgUserIds);\n const members = new Set(\n await this.access.communityMembersAmong(orgId, communityId, Object.keys(roles)),\n );\n return Object.fromEntries(\n Object.entries(roles).filter(([orgUserId]) => members.has(orgUserId)),\n );\n }\n\n private async assertMaySend(\n chats: ChatRepository,\n orgId: string,\n actor: Actor,\n chat: Chat,\n users: ChatUser[],\n settings: DmSettings,\n input: SendMessageInput,\n ): Promise<void> {\n this.assertActive(users, actor.id);\n if (chat.lockedAt) {\n throw new ForbiddenError('This chat is locked');\n }\n if (chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n if (this.pendingCreator(chat, actor)) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n const others = users\n .filter(\n (other) =>\n other.orgUserId !== actor.id && (chat.kind === 'direct' || other.leftAt === null),\n )\n .map((other) => other.orgUserId);\n if (others.length > 0) {\n const roles = await this.personRoles(orgId, chat.communityId, others);\n const recipients = chat.kind === 'group' ? others.filter((other) => roles[other]) : others;\n for (const other of recipients) {\n const role = roles[other];\n if (!role || !this.mayMessage(settings, actor, role)) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n if (recipients.length > 0) {\n const blocked = await chats.blockedWith(orgId, chat.communityId, actor.id, recipients);\n if (blocked.length > 0) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n }\n if (input.replyToId) {\n const reply = await chats.findMessage(orgId, input.replyToId);\n if (!reply || reply.chatId !== chat.id) {\n throw new NotFoundError('Message', input.replyToId);\n }\n }\n await this.requireOwnedImages(orgId, actor.id, input.assetIds);\n }\n\n private async writeMessage(\n chats: ChatRepository,\n orgId: string,\n actor: Actor,\n chat: Chat,\n input: SendMessageInput,\n ): Promise<{ chat: Chat; message: Message; opened: boolean }> {\n const opened =\n chat.kind === 'direct' && chat.status === 'pending' && chat.createdBy !== actor.id;\n const current = opened\n ? await this.mustUpdate(chats, orgId, chat.id, { status: 'open' })\n : chat;\n const message = await chats.insertMessage(\n orgId,\n this.newMessage(orgId, current, actor, input, new Date()),\n );\n if (input.assetIds.length > 0) {\n await chats.attach(orgId, current.communityId, message.id, input.assetIds);\n }\n return { chat: current, message, opened };\n }\n\n private assertMayMessage(settings: DmSettings, actor: Actor, role: Role): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!this.mayMessage(settings, actor, role)) {\n throw new ForbiddenError('You cannot message this person');\n }\n }\n\n private assertGroup(settings: DmSettings): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!settings.groupChats) {\n throw new ForbiddenError('Group chats are off');\n }\n }\n\n private mayMessage(settings: DmSettings, actor: Actor, target: Role): boolean {\n if (isStaffRole(actor.role)) {\n return settings.staffToAnyone;\n }\n if (isStaffRole(target)) {\n return settings.memberToStaff;\n }\n return settings.memberToMember;\n }\n\n private async requireActive(\n repo: ChatRepository,\n orgId: string,\n chatId: string,\n orgUserId: string,\n ): Promise<void> {\n const users = await repo.participants(orgId, chatId);\n this.assertActive(users, orgUserId);\n }\n\n private assertActive(users: ChatUser[], orgUserId: string): void {\n if (!users.some((user) => user.orgUserId === orgUserId && user.leftAt === null)) {\n throw new ForbiddenError('You are not in this chat');\n }\n }\n\n private async requireOwnedImages(\n orgId: string,\n orgUserId: string,\n assetIds: string[],\n ): Promise<void> {\n for (const assetId of assetIds) {\n if (!(await this.images.ownsImage(orgId, assetId, orgUserId))) {\n this.logger.debug('attachment refused', { orgId, orgUserId, assetId });\n throw new NotFoundError('Asset', assetId);\n }\n }\n }\n\n private async requireWritableChat(orgId: string, actor: Actor, chatId: string): Promise<void> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.lockedAt || chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n\n private pendingCreator(chat: Chat, actor: Actor): boolean {\n return chat.kind === 'direct' && chat.status === 'pending' && chat.createdBy === actor.id;\n }\n\n private async mustChat(repo: ChatRepository, orgId: string, chatId: string): Promise<Chat> {\n const chat = await repo.findChat(orgId, chatId);\n if (!chat) {\n throw new NotFoundError('Chat', chatId);\n }\n return chat;\n }\n\n private async mustMessage(\n repo: ChatRepository,\n orgId: string,\n messageId: string,\n ): Promise<Message> {\n const message = await repo.findMessage(orgId, messageId);\n if (!message) {\n throw new NotFoundError('Message', messageId);\n }\n return message;\n }\n\n private async mustUpdate(\n chats: ChatRepository,\n orgId: string,\n chatId: string,\n patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>,\n ): Promise<Chat> {\n const next = await chats.updateChat(orgId, chatId, patch);\n if (!next) {\n throw new NotFoundError('Chat', chatId);\n }\n return next;\n }\n\n private memberRow(\n orgId: string,\n communityId: string,\n chatId: string,\n orgUserId: string,\n now: Date,\n ): ChatUser {\n return {\n orgId,\n communityId,\n chatId,\n orgUserId,\n joinedAt: now,\n leftAt: null,\n lastReadMessageId: null,\n muted: false,\n mutedUntil: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n private newMessage(\n orgId: string,\n chat: Chat,\n actor: Actor,\n input: SendMessageInput,\n now: Date,\n ): Message {\n return {\n orgId,\n id: genId('message'),\n communityId: chat.communityId,\n chatId: chat.id,\n orgUserId: actor.id,\n body: input.body,\n replyToId: input.replyToId,\n editedAt: null,\n deletedAt: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n private activeIds(users: ChatUser[]): string[] {\n return users\n .filter((user) => user.leftAt === null)\n .map((user) => user.orgUserId)\n .sort();\n }\n\n private chatData(chat: Chat, users: ChatUser[]) {\n return { ...chat, participantIds: this.activeIds(users) };\n }\n\n private meta(actor: Actor) {\n return { actorOrgUserId: actor.id };\n }\n\n private createdEvent(orgId: string, actor: Actor, chat: Chat, users: ChatUser[]) {\n return discussionEvents.chatCreated.make({\n orgId,\n data: this.chatData(chat, users),\n metadata: this.meta(actor),\n });\n }\n\n private updatedEvent(orgId: string, actor: Actor, chat: Chat, users: ChatUser[]) {\n return discussionEvents.chatUpdated.make({\n orgId,\n data: this.chatData(chat, users),\n metadata: this.meta(actor),\n });\n }\n\n private addedEvent(orgId: string, actor: Actor, chat: Chat, orgUserId: string) {\n return discussionEvents.chatParticipantAdded.make({\n orgId,\n data: {\n chatId: chat.id,\n communityId: chat.communityId,\n orgUserId,\n addedBy: actor.id,\n },\n metadata: this.meta(actor),\n });\n }\n\n private sentEvent(orgId: string, actor: Actor, chat: Chat, message: Message) {\n return discussionEvents.messageSent.make({\n orgId,\n data: message,\n metadata: { ...this.meta(actor), chatStatus: chat.status },\n });\n }\n\n private async viewOf(orgId: string, actor: Actor, message: Message): Promise<MessageView> {\n const [view] = await this.withSenders(orgId, actor, [message]);\n return view!;\n }\n\n private async withSenders(\n orgId: string,\n actor: Actor,\n messages: Message[],\n ): Promise<MessageView[]> {\n if (messages.length === 0) {\n return [];\n }\n const messageIds = messages.map((message) => message.id);\n const [records, attached, reactions] = await Promise.all([\n this.profiles.authorsOf(orgId, [...new Set(messages.map((message) => message.orgUserId))]),\n this.repo.attachmentsOf(orgId, messageIds),\n this.repo.reactionsOf(orgId, messageIds, actor.id),\n ]);\n const shown = (message: Message) =>\n message.deletedAt !== null && !isStaffRole(actor.role) ? [] : (attached[message.id] ?? []);\n return messages.map((message) => {\n const record = records[message.orgUserId];\n if (!record) {\n throw new NotFoundError('Member', message.orgUserId);\n }\n const counts = reactions[message.id];\n return {\n ...this.present(message, actor),\n sender: authorView(orgId, record, this.fileUrls),\n attachments: shown(message).map((assetId) => ({\n assetId,\n url: this.fileUrls.messageAttachment(message.chatId, message.id, assetId),\n })),\n reactions: counts?.reactions ?? {},\n viewerReaction: counts?.viewerReaction ?? null,\n };\n });\n }\n\n private present(message: Message, actor: Actor): Message {\n if (message.deletedAt !== null && !isStaffRole(actor.role)) {\n return { ...message, body: '' };\n }\n return message;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"chats.js","names":[],"sources":["../../src/discussion/chats.ts"],"sourcesContent":["import { genId } from '../shared/id.js';\nimport {\n ConflictError,\n ForbiddenError,\n InvalidInputError,\n NotFoundError,\n UnavailableError,\n} from '../shared/errors.js';\nimport type { Logger, PubSub } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type { DmSettings } from '../content/index.js';\nimport type { AssetFiles, FileUrls, MemberImageReader } from '../assets/index.js';\nimport type {\n Chat,\n ChatLiveEvent,\n ChatUnreadSummary,\n ChatUser,\n LiveMessage,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Page,\n ReactionCounts,\n ReactionEmoji,\n} from '../types/index.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { NewDiscussionEvent } from './events.js';\nimport { discussionEvents } from './events.js';\nimport { authorName, authorView } from './authors.js';\nimport { activeSubscription } from './subscriptions.js';\nimport { keepAllowed, mentionTokens, mentionViews } from './mentions.js';\nimport { isTeam, teamBadge } from './rules.js';\nimport { ChatHub, TYPING, publishChange, type ChatHubOptions } from './live.js';\nimport type { CommentAuthor } from './model.js';\nimport type {\n AuthorPermissionReader,\n AuthorProfileReader,\n ChatInboxRow,\n ChatListItem,\n ChatListQuery,\n ChatLiveReads,\n ChatMemberStates,\n ChatRepository,\n ChatService,\n ChatUnread,\n ChatUnitOfWork,\n LiveChat,\n MentionsRepository,\n MessageQueue,\n MessageQueueEntry,\n MessageReactions,\n SendMessageInput,\n StartDirectInput,\n StartGroupInput,\n CommunityAccess,\n SpaceReader,\n SubscriptionsRepository,\n} from './ports.js';\nimport { MODERATION_QUEUE_LIMIT } from './ports.js';\n\nconst TYPING_FOR_MS = 5000;\nconst TYPING_REPEAT_MS = 2000;\n\nexport type ChatServiceParams = {\n repo: ChatRepository;\n access: CommunityAccess;\n content: Pick<SpaceReader, 'getDmSettings'>;\n authors: AuthorPermissionReader;\n profiles: AuthorProfileReader;\n subscriptions: SubscriptionsRepository;\n mentions: MentionsRepository;\n images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n fileUrls: FileUrls;\n uow: ChatUnitOfWork;\n pubsub?: PubSub;\n live?: ChatHubOptions;\n logger?: Logger;\n};\n\nfunction eventMessage(message: Message): Omit<Message, 'clientId'> {\n const { clientId: _clientId, ...rest } = message;\n return rest;\n}\n\ntype Teams = { asked: ReadonlySet<string>; team: Map<string, boolean> };\n\nexport class ChatServiceImpl implements ChatService, ChatLiveReads {\n /** This process's live chat hub; null without a PubSub. */\n readonly hub: ChatHub | null;\n private readonly repo: ChatRepository;\n private readonly access: CommunityAccess;\n private readonly content: Pick<SpaceReader, 'getDmSettings'>;\n private readonly authors: AuthorPermissionReader;\n private readonly profiles: AuthorProfileReader;\n private readonly subscriptions: SubscriptionsRepository;\n private readonly mentions: MentionsRepository;\n private readonly images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n private readonly fileUrls: FileUrls;\n private readonly uow: ChatUnitOfWork;\n private readonly pubsub: PubSub | null;\n private readonly logger: Logger;\n private readonly typed = new Map<string, number>();\n\n constructor(params: ChatServiceParams) {\n this.repo = params.repo;\n this.access = params.access;\n this.content = params.content;\n this.authors = params.authors;\n this.profiles = params.profiles;\n this.subscriptions = params.subscriptions;\n this.mentions = params.mentions;\n this.images = params.images;\n this.fileUrls = params.fileUrls;\n this.uow = params.uow;\n this.pubsub = params.pubsub ?? null;\n this.logger = params.logger ?? noopLogger;\n this.hub = this.pubsub\n ? new ChatHub({ pubsub: this.pubsub, reads: this, logger: this.logger, ...params.live })\n : null;\n }\n\n async startDirect(input: StartDirectInput, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n if (actor.id === input.orgUserId) {\n throw new InvalidInputError('You cannot message yourself');\n }\n if (input.body === null && input.assetIds.length > 0) {\n throw new InvalidInputError('An attachment needs a message');\n }\n const settings = await this.requireSettings(orgId, input.communityId);\n await this.requireActor(actor, input.communityId);\n const targetTeam = await this.requirePerson(orgId, input.communityId, input.orgUserId);\n const actorTeam = isTeam(actor.permissions, input.communityId);\n this.assertMayMessage(settings, actorTeam, targetTeam);\n const first: SendMessageInput | null =\n input.body === null ? null : { body: input.body, replyToId: null, assetIds: input.assetIds };\n await this.requireOwnedImages(orgId, actor.id, input.assetIds);\n const teams: Teams = {\n asked: new Set([input.orgUserId]),\n team: new Map([[input.orgUserId, targetTeam]]),\n };\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n await chats.lockDirectPair(orgId, input.communityId, actor.id, input.orgUserId);\n const existing = await chats.findDirect(orgId, input.communityId, actor.id, input.orgUserId);\n if (existing) {\n const users = await chats.participants(orgId, existing.id);\n const rejoin = users.some((user) => user.orgUserId === actor.id && user.leftAt !== null);\n const present = users.map((user) =>\n user.orgUserId === actor.id ? { ...user, leftAt: null } : user,\n );\n if (first) {\n await this.assertMaySend(chats, orgId, actor, existing, present, settings, first, teams);\n }\n if (rejoin) {\n await chats.join(\n orgId,\n this.memberRow(orgId, existing.communityId, existing.id, actor.id, new Date()),\n );\n }\n const written = first\n ? await this.writeMessage(chats, mentions, orgId, actor, existing, present, first)\n : null;\n const chat = written?.chat ?? existing;\n const events: NewDiscussionEvent[] = [];\n if (rejoin || written?.opened) {\n events.push(this.updatedEvent(orgId, actor, chat, present));\n }\n if (written) {\n events.push(\n this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned),\n );\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n return { chat, created: false };\n }\n if (await chats.isBlocked(orgId, input.communityId, actor.id, input.orgUserId)) {\n throw new ForbiddenError('You cannot message this person');\n }\n const request = settings.firstMessageIsRequest && !actorTeam && !targetTeam;\n if (request && !first) {\n throw new InvalidInputError('A message request needs a message');\n }\n const now = new Date();\n const chat = await chats.insertChat(orgId, {\n orgId,\n id: genId('chat'),\n communityId: input.communityId,\n kind: 'direct',\n status: request ? 'pending' : 'open',\n createdBy: actor.id,\n lockedAt: null,\n lockedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n const users = [actor.id, input.orgUserId].map((orgUserId) =>\n this.memberRow(orgId, chat.communityId, chat.id, orgUserId, now),\n );\n for (const user of users) {\n await chats.join(orgId, user);\n }\n const events: NewDiscussionEvent[] = [this.createdEvent(orgId, actor, chat, users)];\n if (first) {\n const written = await this.writeMessage(chats, mentions, orgId, actor, chat, users, first);\n events.push(this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned));\n }\n await outbox.append(events);\n return { chat, created: true };\n });\n if (saved.created) {\n this.logger.info('direct chat started', {\n orgId,\n communityId: input.communityId,\n chatId: saved.chat.id,\n status: saved.chat.status,\n actorId: actor.id,\n });\n } else {\n this.logger.debug('direct chat already exists', {\n orgId,\n communityId: input.communityId,\n chatId: saved.chat.id,\n });\n }\n return saved.chat;\n }\n\n async startGroup(input: StartGroupInput, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const settings = await this.requireSettings(orgId, input.communityId);\n this.assertGroup(settings);\n await this.requireActor(actor, input.communityId);\n const others = [...new Set(input.orgUserIds.filter((id) => id !== actor.id))].sort();\n if (others.length < 2) {\n throw new InvalidInputError('A group chat needs three people');\n }\n if (others.length + 1 > settings.groupSize) {\n throw new ConflictError('This group is full');\n }\n const actorTeam = isTeam(actor.permissions, input.communityId);\n for (const orgUserId of others) {\n const targetTeam = await this.requirePerson(orgId, input.communityId, orgUserId);\n this.assertMayMessage(settings, actorTeam, targetTeam);\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n if (await chats.anyBlockedAmong(orgId, input.communityId, [actor.id, ...others])) {\n throw new ForbiddenError('You cannot message this person');\n }\n const now = new Date();\n const chat = await chats.insertChat(orgId, {\n orgId,\n id: genId('chat'),\n communityId: input.communityId,\n kind: 'group',\n status: 'open',\n createdBy: actor.id,\n lockedAt: null,\n lockedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n const ids = [actor.id, ...others];\n const users = ids.map((orgUserId) =>\n this.memberRow(orgId, chat.communityId, chat.id, orgUserId, now),\n );\n for (const user of users) {\n await chats.join(orgId, user);\n }\n const events: NewDiscussionEvent[] = [\n this.createdEvent(orgId, actor, chat, users),\n ...others.map((orgUserId) => this.addedEvent(orgId, actor, chat, orgUserId)),\n ];\n await outbox.append(events);\n return chat;\n });\n this.logger.info('group chat started', {\n orgId,\n communityId: input.communityId,\n chatId: saved.id,\n actorId: actor.id,\n });\n return saved;\n }\n\n async addParticipant(chatId: string, orgUserId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.kind !== 'group') {\n throw new ConflictError('This chat is not a group');\n }\n const settings = await this.requireSettings(orgId, chat.communityId);\n this.assertGroup(settings);\n const targetTeam = await this.requirePerson(orgId, chat.communityId, orgUserId);\n this.assertMayMessage(settings, isTeam(actor.permissions, chat.communityId), targetTeam);\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n this.assertActive(users, actor.id);\n const existing = users.find((user) => user.orgUserId === orgUserId);\n if (existing && existing.leftAt === null) {\n throw new ConflictError('That person is already in this chat');\n }\n const active = users.filter((user) => user.leftAt === null);\n if (active.length + 1 > settings.groupSize) {\n throw new ConflictError('This group is full');\n }\n const blocked = await chats.blockedWith(\n orgId,\n current.communityId,\n orgUserId,\n active.map((user) => user.orgUserId),\n );\n if (blocked.length > 0) {\n throw new ForbiddenError('You cannot message this person');\n }\n const now = new Date();\n await chats.join(\n orgId,\n this.memberRow(orgId, current.communityId, current.id, orgUserId, now),\n );\n await outbox.append([this.addedEvent(orgId, actor, current, orgUserId)]);\n return current;\n });\n this.logger.info('chat participant added', { orgId, chatId, orgUserId, actorId: actor.id });\n return saved;\n }\n\n async leave(chatId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n this.assertActive(users, actor.id);\n const now = new Date();\n await chats.markLeft(orgId, chatId, actor.id, now);\n const remaining = users.map((user) =>\n user.orgUserId === actor.id ? { ...user, leftAt: now } : user,\n );\n await outbox.append([this.updatedEvent(orgId, actor, current, remaining)]);\n return current;\n });\n this.logger.info('chat left', { orgId, chatId, actorId: actor.id });\n return saved;\n }\n\n async accept(chatId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.createdBy === actor.id) {\n throw new ForbiddenError('You cannot accept this request');\n }\n if (chat.kind !== 'direct' || chat.status !== 'pending') {\n throw new ConflictError('This chat is not a request');\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n if (current.kind !== 'direct' || current.status !== 'pending') {\n throw new ConflictError('This chat is not a request');\n }\n const next = await this.mustUpdate(chats, orgId, chatId, { status: 'open' });\n const users = await chats.participants(orgId, chatId);\n await outbox.append([this.updatedEvent(orgId, actor, next, users)]);\n return next;\n });\n this.logger.info('chat accepted', { orgId, chatId, actorId: actor.id });\n return saved;\n }\n\n async send(chatId: string, input: SendMessageInput, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const preview = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, preview.communityId);\n const settings = await this.requireSettings(orgId, preview.communityId);\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n const clientId = input.clientId;\n const before = await this.repo.participants(orgId, chatId);\n const teams = await this.teamsOf(\n orgId,\n preview.communityId,\n before.map((user) => user.orgUserId).filter((orgUserId) => orgUserId !== actor.id),\n );\n await this.requireOwnedImages(orgId, actor.id, input.assetIds);\n let saved: { message: Message; repeat: boolean };\n try {\n saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const chat = await this.mustChat(chats, orgId, chatId);\n if (clientId) {\n const stored = await chats.findByClientId(orgId, chatId, actor.id, clientId);\n if (stored) {\n return { message: stored, repeat: true };\n }\n }\n const users = await chats.participants(orgId, chatId);\n await this.assertMaySend(chats, orgId, actor, chat, users, settings, input, teams);\n const written = await this.writeMessage(chats, mentions, orgId, actor, chat, users, input);\n const events: NewDiscussionEvent[] = [];\n if (written.opened) {\n events.push(this.updatedEvent(orgId, actor, written.chat, users));\n }\n events.push(this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned));\n await outbox.append(events);\n return { message: written.message, repeat: false };\n });\n } catch (error) {\n const stored =\n clientId && error instanceof ConflictError\n ? await this.repo.findByClientId(orgId, chatId, actor.id, clientId)\n : null;\n if (!stored) {\n throw error;\n }\n saved = { message: stored, repeat: true };\n }\n if (saved.repeat) {\n this.logger.debug('message send repeated', {\n orgId,\n chatId,\n messageId: saved.message.id,\n });\n } else {\n this.logger.info('message sent', {\n orgId,\n chatId,\n messageId: saved.message.id,\n actorId: actor.id,\n });\n }\n return this.viewOf(actor, saved.message);\n }\n\n async editMessage(messageId: string, body: string, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireWritableChat(actor, message.chatId);\n if (message.orgUserId !== actor.id) {\n throw new ForbiddenError('You can change only your own message');\n }\n if (message.deletedAt) {\n throw new ConflictError('This message was deleted');\n }\n if (message.body === body) {\n return this.viewOf(actor, message);\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const next = await chats.updateMessage(orgId, messageId, { body, editedAt: now });\n if (!next) {\n throw new NotFoundError('Message', messageId);\n }\n const chat = await this.mustChat(chats, orgId, next.chatId);\n const users = await chats.participants(orgId, next.chatId);\n const candidates = this.mentionable(body, users);\n const added = await mentions.sync(orgId, 'message', messageId, candidates, now);\n await outbox.append([\n discussionEvents.messageUpdated.make({\n orgId,\n data: { ...eventMessage(next), mentionedOrgUserIds: keepAllowed(candidates, added) },\n metadata: { ...this.meta(actor), chatStatus: chat.status },\n }),\n ]);\n return next;\n });\n this.logger.info('message edited', { orgId, messageId, actorId: actor.id });\n return this.viewOf(actor, saved);\n }\n\n async deleteMessage(messageId: string, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n if (!actor.permissions.has('communities:moderate', message.communityId)) {\n if (message.orgUserId !== actor.id) {\n this.logger.warn('message delete refused — not the sender or a moderator', {\n orgId,\n messageId,\n actorId: actor.id,\n });\n throw new ForbiddenError('You cannot delete this message');\n }\n await this.requireWritableChat(actor, message.chatId);\n }\n if (message.deletedAt) {\n throw new ConflictError('This message was deleted');\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const next = await chats.updateMessage(orgId, messageId, { deletedAt: now });\n if (!next) {\n throw new NotFoundError('Message', messageId);\n }\n await mentions.removeItem(orgId, 'message', messageId);\n await chats.resolveFlags(orgId, messageId, {\n resolvedAt: now,\n resolvedBy: actor.id,\n resolution: 'deleted',\n });\n await outbox.append([\n discussionEvents.messageDeleted.make({\n orgId,\n data: eventMessage(next),\n metadata: this.meta(actor),\n }),\n ]);\n return next;\n });\n this.logger.info('message deleted', { orgId, messageId, actorId: actor.id });\n return this.viewOf(actor, saved);\n }\n\n async setReaction(\n messageId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<MessageReactions> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n await this.uow.run(async ({ chats, outbox }) => {\n const change = await chats.setReaction(\n orgId,\n message.communityId,\n messageId,\n actor.id,\n emoji,\n );\n if (!change.changed) {\n return;\n }\n const where = {\n messageId,\n chatId: message.chatId,\n communityId: message.communityId,\n orgUserId: actor.id,\n };\n const events: NewDiscussionEvent[] = [];\n if (change.inserted && emoji !== null) {\n events.push(\n discussionEvents.messageReactionAdded.make({\n orgId,\n data: { ...where, reaction: emoji },\n metadata: this.meta(actor),\n }),\n );\n }\n events.push(\n discussionEvents.messageReactionsChanged.make({\n orgId,\n data: { ...where, reaction: emoji },\n metadata: this.meta(actor),\n }),\n );\n await outbox.append(events);\n });\n this.logger.debug('message reaction set', { orgId, messageId });\n const counts = await this.repo.reactionsOf(orgId, [messageId], actor.id);\n return counts[messageId] ?? { reactions: {} };\n }\n\n async markRead(chatId: string, messageId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (message.chatId !== chatId) {\n throw new NotFoundError('Message', messageId);\n }\n const moved = await this.uow.run(async ({ chats, outbox }) => {\n if (!(await chats.setRead(orgId, chatId, actor.id, messageId))) {\n return false;\n }\n await outbox.append([\n discussionEvents.chatRead.make({\n orgId,\n data: {\n chatId,\n communityId: chat.communityId,\n orgUserId: actor.id,\n lastReadMessageId: messageId,\n },\n metadata: this.meta(actor),\n }),\n ]);\n return true;\n });\n this.logger.debug(moved ? 'chat marked read' : 'chat read mark kept', {\n orgId,\n chatId,\n messageId,\n });\n }\n\n async block(communityId: string, orgUserId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n await this.requirePerson(orgId, communityId, orgUserId);\n await this.uow.run(async ({ chats }) => {\n await chats.lockDirectPair(orgId, communityId, actor.id, orgUserId);\n await chats.block(orgId, communityId, actor.id, orgUserId);\n });\n this.logger.info('user blocked', { orgId, communityId, actorId: actor.id });\n }\n\n async unblock(communityId: string, orgUserId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n await this.uow.run(async ({ chats }) => {\n await chats.lockDirectPair(orgId, communityId, actor.id, orgUserId);\n await chats.unblock(orgId, communityId, actor.id, orgUserId);\n });\n this.logger.info('user unblocked', { orgId, communityId, actorId: actor.id });\n }\n\n async flagMessage(messageId: string, reason: string, actor: Actor): Promise<MessageFlag> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const flag = await chats.insertFlag(orgId, {\n orgId,\n id: genId('messageFlag'),\n communityId: message.communityId,\n messageId,\n orgUserId: actor.id,\n reason,\n resolvedAt: null,\n resolution: null,\n resolvedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n if (!flag) {\n const existing = await chats.findFlag(orgId, messageId, actor.id);\n if (!existing) {\n throw new NotFoundError('Message', messageId);\n }\n return existing;\n }\n await outbox.append([\n discussionEvents.messageReported.make({\n orgId,\n data: flag,\n metadata: this.meta(actor),\n }),\n ]);\n return flag;\n });\n this.logger.info('message flagged', { orgId, messageId, actorId: actor.id });\n return saved;\n }\n\n async resolveMessageFlags(\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (communityId !== null && message.communityId !== communityId) {\n throw new NotFoundError('Message', messageId);\n }\n this.requireModerator(actor, message.communityId, 'resolve message flags');\n await this.requireActor(actor, message.communityId);\n await this.repo.resolveFlags(orgId, messageId, {\n resolvedAt: new Date(),\n resolvedBy: actor.id,\n resolution,\n });\n this.logger.info('message flags resolved', {\n orgId,\n communityId: message.communityId,\n messageId,\n resolution,\n actorId: actor.id,\n });\n }\n\n async lock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat> {\n return this.setLock(actor, chatId, true, communityId);\n }\n\n async unlock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat> {\n return this.setLock(actor, chatId, false, communityId);\n }\n\n async listChats(\n communityId: string,\n query: ChatListQuery,\n actor: Actor,\n ): Promise<Page<ChatListItem>> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n const page = await this.repo.listForUser(orgId, communityId, actor.id, query);\n const items = await this.listItems(actor, communityId, page.rows);\n this.logger.debug('chats listed', { orgId, communityId, count: items.length });\n return { ...page, rows: items };\n }\n\n async chat(chatId: string, actor: Actor): Promise<ChatListItem> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const row = await this.repo.inboxRow(orgId, chatId, actor.id);\n if (!row) {\n throw new NotFoundError('Chat', chatId);\n }\n const [item] = await this.listItems(actor, chat.communityId, [row]);\n this.logger.debug('chat read', { orgId, chatId });\n return item!;\n }\n\n async directChat(communityId: string, orgUserId: string, actor: Actor): Promise<ChatListItem> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n const chat =\n orgUserId === actor.id\n ? null\n : await this.repo.findDirect(orgId, communityId, actor.id, orgUserId);\n const row = chat ? await this.repo.inboxRow(orgId, chat.id, actor.id) : null;\n if (!row) {\n throw new NotFoundError('Chat', orgUserId);\n }\n const [item] = await this.listItems(actor, communityId, [row]);\n this.logger.debug('direct chat read', { orgId, communityId, chatId: row.chat.id });\n return item!;\n }\n\n async unreadSummary(communityId: string, actor: Actor): Promise<ChatUnreadSummary> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n return this.repo.unreadSummary(orgId, communityId, actor.id, new Date());\n }\n\n async typing(chatId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.lockedAt || chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n if (!this.pubsub) {\n return;\n }\n const now = Date.now();\n const key = `${orgId}:${chatId}:${actor.id}`;\n const last = this.typed.get(key);\n if (last !== undefined && now - last < TYPING_REPEAT_MS) {\n return;\n }\n this.typed.set(key, now);\n for (const [seen, at] of this.typed) {\n if (now - at >= TYPING_REPEAT_MS) {\n this.typed.delete(seen);\n }\n }\n await publishChange(\n this.pubsub,\n {\n event: TYPING,\n orgId,\n communityId: chat.communityId,\n chatId,\n orgUserId: actor.id,\n until: new Date(now + TYPING_FOR_MS).toISOString(),\n },\n this.logger,\n );\n }\n\n async watch(communityId: string, actor: Actor): Promise<AsyncIterable<ChatLiveEvent>> {\n if (!this.hub) {\n throw new UnavailableError('Live chat is not available');\n }\n await this.requireSettings(actor.orgId, communityId);\n await this.requireActor(actor, communityId);\n return this.hub.open({ communityId, actor });\n }\n\n async messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]> {\n const messages = await this.repo.messagesByIds(orgId, [...new Set(messageIds)]);\n const views = await this.withSenders(orgId, null, messages);\n return views.map(({ viewerReaction: _viewerReaction, ...view }) => view);\n }\n\n activeParticipants(orgId: string, chatId: string): Promise<string[]> {\n return this.repo.activeParticipants(orgId, chatId);\n }\n\n async messageReactions(orgId: string, messageId: string): Promise<ReactionCounts> {\n const counts = await this.repo.reactionsOf(orgId, [messageId], '');\n return counts[messageId]?.reactions ?? {};\n }\n\n async liveChat(orgId: string, chatId: string): Promise<LiveChat | null> {\n const chat = await this.repo.findChat(orgId, chatId);\n if (!chat) {\n return null;\n }\n const participantIds = await this.repo.activeParticipants(orgId, chatId);\n const profiles = await this.participantsOf(orgId, chat.communityId, participantIds);\n return {\n chat,\n participantIds,\n participants: participantIds.flatMap((id) => profiles.get(id) ?? []),\n };\n }\n\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<ChatMemberStates[]> {\n return this.repo.memberStates(orgId, communityId, orgUserIds, new Date());\n }\n\n async messages(\n chatId: string,\n query: { page: number; pageSize: number },\n actor: Actor,\n ): Promise<Page<MessageView>> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const page = await this.repo.listMessages(orgId, chatId, query);\n this.logger.debug('chat messages read', { orgId, chatId });\n return {\n ...page,\n rows: await this.withSenders(orgId, actor, page.rows),\n };\n }\n\n async attachmentFile(\n chatId: string,\n messageId: string,\n assetId: string,\n actor: Actor,\n ): Promise<string> {\n const orgId = actor.orgId;\n const chat = await this.repo.findChat(orgId, chatId);\n const message = await this.repo.findMessage(orgId, messageId);\n if (!chat || !message || message.chatId !== chatId) {\n throw new NotFoundError('Message attachment', assetId);\n }\n const attached = (await this.repo.attachmentsOf(orgId, [messageId]))[messageId] ?? [];\n if (!attached.includes(assetId)) {\n throw new NotFoundError('Message attachment', assetId);\n }\n if (!(await this.access.isCommunityMember(chat.communityId, actor))) {\n throw new NotFoundError('Message attachment', assetId);\n }\n const moderates = actor.permissions.has('communities:moderate', chat.communityId);\n const users = await this.repo.participants(orgId, chatId);\n const inChat = users.some((user) => user.orgUserId === actor.id && user.leftAt === null);\n const readable = inChat\n ? message.deletedAt === null || moderates\n : moderates &&\n (await this.repo.listOpenFlags(orgId, chat.communityId)).some(\n (flag) => flag.messageId === messageId,\n );\n if (!readable) {\n throw new NotFoundError('Message attachment', assetId);\n }\n const url = await this.images.signFile(orgId, assetId);\n if (!url) {\n throw new NotFoundError('Message attachment', assetId);\n }\n return url;\n }\n\n async messageQueue(communityId: string, actor: Actor): Promise<MessageQueue> {\n const orgId = actor.orgId;\n this.requireModerator(actor, communityId, 'message queue');\n await this.requireActor(actor, communityId);\n await this.requireSettings(orgId, communityId);\n const flags = await this.repo.listOpenFlags(orgId, communityId, MODERATION_QUEUE_LIMIT);\n const messages = await this.repo.messagesByIds(orgId, [\n ...new Set(flags.map((flag) => flag.messageId)),\n ]);\n const summaries = await this.repo.chatSummaries(orgId, [\n ...new Set(messages.map((message) => message.chatId)),\n ]);\n const byChat = new Map(summaries.map((summary) => [summary.chat.id, summary]));\n const views = await this.withSenders(orgId, actor, messages);\n const entries: MessageQueueEntry[] = views.flatMap((message) => {\n const summary = byChat.get(message.chatId);\n if (!summary) {\n return [];\n }\n return [\n {\n message,\n flags: flags.filter((flag) => flag.messageId === message.id),\n chat: summary.chat,\n participantIds: summary.participantIds,\n },\n ];\n });\n this.logger.debug('message queue read', { orgId, communityId, count: entries.length });\n return { entries };\n }\n\n async chatSummary(\n orgId: string,\n chatId: string,\n ): Promise<{ chat: Chat; participants: ChatUser[] } | null> {\n const chat = await this.repo.findChat(orgId, chatId);\n if (!chat) {\n return null;\n }\n const participants = await this.repo.participants(orgId, chatId);\n return { chat, participants };\n }\n\n async titlesFor(chatIds: string[], actor: Actor): Promise<Record<string, string>> {\n const orgId = actor.orgId;\n if (chatIds.length === 0) {\n return {};\n }\n const summaries = (await this.repo.chatSummaries(orgId, [...new Set(chatIds)])).filter(\n (summary) => summary.participantIds.includes(actor.id),\n );\n const others = [\n ...new Set(\n summaries.flatMap((summary) => summary.participantIds.filter((id) => id !== actor.id)),\n ),\n ];\n const records = others.length > 0 ? await this.profiles.authorsOf(orgId, others) : {};\n const nameOf = (id: string): string => {\n const record = records[id];\n return (record && authorName(record)) || 'A member';\n };\n return Object.fromEntries(\n summaries.flatMap((summary) => {\n const names = summary.participantIds.filter((id) => id !== actor.id).map(nameOf);\n if (names.length > 0) {\n return [[summary.chat.id, names.join(', ')]];\n }\n return summary.chat.kind === 'group' ? [[summary.chat.id, 'Group chat']] : [];\n }),\n );\n }\n\n unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread> {\n return this.repo.unreadFor(orgId, chatId, orgUserId);\n }\n\n findMessage(orgId: string, messageId: string): Promise<Message | null> {\n return this.repo.findMessage(orgId, messageId);\n }\n\n async latestMessageBody(orgId: string, chatId: string): Promise<string | null> {\n const page = await this.repo.listMessages(orgId, chatId, { page: 1, pageSize: 1 });\n return page.rows[0]?.body ?? null;\n }\n\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean> {\n return this.repo.isBlocked(orgId, communityId, a, b);\n }\n\n private async setLock(\n actor: Actor,\n chatId: string,\n locked: boolean,\n communityId: string | null,\n ): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n if (communityId !== null && chat.communityId !== communityId) {\n throw new NotFoundError('Chat', chatId);\n }\n this.requireModerator(actor, chat.communityId, locked ? 'lock chat' : 'unlock chat');\n await this.requireActor(actor, chat.communityId);\n if (Boolean(chat.lockedAt) === locked) {\n return chat;\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const next = await this.mustUpdate(chats, orgId, chatId, {\n lockedAt: locked ? now : null,\n lockedBy: locked ? actor.id : null,\n });\n if (locked) {\n await chats.resolveOpenFlagsForChat(orgId, chatId, {\n resolvedAt: now,\n resolvedBy: actor.id,\n resolution: 'locked',\n });\n }\n const users = await chats.participants(orgId, chatId);\n await outbox.append([this.updatedEvent(orgId, actor, next, users)]);\n return next;\n });\n this.logger.info(locked ? 'chat locked' : 'chat unlocked', {\n orgId,\n chatId,\n actorId: actor.id,\n });\n return saved;\n }\n\n private requireModerator(actor: Actor, communityId: string, action: string): void {\n if (!actor.permissions.has('communities:moderate', communityId)) {\n this.logger.warn('chat moderation refused — not a moderator', {\n orgId: actor.orgId,\n communityId,\n action,\n actorId: actor.id,\n });\n throw new ForbiddenError('You need communities:moderate');\n }\n }\n\n private async requireSettings(orgId: string, communityId: string): Promise<DmSettings> {\n const settings = await this.content.getDmSettings(communityId, this.authors.systemActor(orgId));\n if (!settings) {\n throw new NotFoundError('Community', communityId);\n }\n return settings;\n }\n\n private async requireActor(actor: Actor, communityId: string): Promise<void> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new ForbiddenError('You are not a member of this community');\n }\n }\n\n /** Whether the person is team in the community; throws when they are not a\n * member of it. */\n private async requirePerson(\n orgId: string,\n communityId: string,\n orgUserId: string,\n ): Promise<boolean> {\n const teams = await this.membersTeam(orgId, communityId, [orgUserId]);\n const team = teams.get(orgUserId);\n if (team === undefined) {\n throw new ForbiddenError('That person is not a member of this community');\n }\n return team;\n }\n\n /** Each member of the community among the given people, with whether they\n * are team there. People who are not members are left out. */\n private async membersTeam(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<Map<string, boolean>> {\n const members = await this.access.communityMembersAmong(orgId, communityId, orgUserIds);\n if (members.length === 0) {\n return new Map();\n }\n const permissions = await this.authors.permissionsOfMany(orgId, members);\n return new Map(\n members.map((orgUserId) => {\n const held = permissions.get(orgUserId);\n return [orgUserId, held !== undefined && isTeam(held, communityId)];\n }),\n );\n }\n\n private async teamsOf(orgId: string, communityId: string, orgUserIds: string[]): Promise<Teams> {\n return {\n asked: new Set(orgUserIds),\n team: await this.membersTeam(orgId, communityId, orgUserIds),\n };\n }\n\n private async assertMaySend(\n chats: ChatRepository,\n orgId: string,\n actor: Actor,\n chat: Chat,\n users: ChatUser[],\n settings: DmSettings,\n input: SendMessageInput,\n teams: Teams,\n ): Promise<void> {\n this.assertActive(users, actor.id);\n if (chat.lockedAt) {\n throw new ForbiddenError('This chat is locked');\n }\n if (chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n if (this.pendingCreator(chat, actor)) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n const others = users\n .filter(\n (other) =>\n other.orgUserId !== actor.id && (chat.kind === 'direct' || other.leftAt === null),\n )\n .map((other) => other.orgUserId);\n if (others.some((other) => !teams.asked.has(other))) {\n throw new ConflictError('This chat changed while you were sending; send again');\n }\n if (others.length > 0) {\n const recipients =\n chat.kind === 'group' ? others.filter((other) => teams.team.has(other)) : others;\n const actorTeam = isTeam(actor.permissions, chat.communityId);\n for (const other of recipients) {\n const team = teams.team.get(other);\n if (team === undefined || !this.mayMessage(settings, actorTeam, team)) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n if (recipients.length > 0) {\n const blocked = await chats.blockedWith(orgId, chat.communityId, actor.id, recipients);\n if (blocked.length > 0) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n }\n if (input.replyToId) {\n const reply = await chats.findMessage(orgId, input.replyToId);\n if (!reply || reply.chatId !== chat.id) {\n throw new NotFoundError('Message', input.replyToId);\n }\n }\n }\n\n private mentionable(body: string, users: ChatUser[]): string[] {\n return keepAllowed(\n mentionTokens(body),\n users.filter((user) => user.leftAt === null).map((user) => user.orgUserId),\n );\n }\n\n private async writeMessage(\n chats: ChatRepository,\n mentions: MentionsRepository,\n orgId: string,\n actor: Actor,\n chat: Chat,\n users: ChatUser[],\n input: SendMessageInput,\n ): Promise<{ chat: Chat; message: Message; opened: boolean; mentioned: string[] }> {\n const opened =\n chat.kind === 'direct' && chat.status === 'pending' && chat.createdBy !== actor.id;\n const current = opened\n ? await this.mustUpdate(chats, orgId, chat.id, { status: 'open' })\n : chat;\n const message = await chats.insertMessage(\n orgId,\n this.newMessage(orgId, current, actor, input, new Date()),\n );\n if (input.assetIds.length > 0) {\n await chats.attach(orgId, current.communityId, message.id, input.assetIds);\n }\n const mentioned = this.mentionable(input.body, users);\n await mentions.sync(orgId, 'message', message.id, mentioned, message.createdAt);\n return { chat: current, message, opened, mentioned };\n }\n\n private assertMayMessage(settings: DmSettings, actorTeam: boolean, targetTeam: boolean): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!this.mayMessage(settings, actorTeam, targetTeam)) {\n throw new ForbiddenError('You cannot message this person');\n }\n }\n\n private assertGroup(settings: DmSettings): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!settings.groupChats) {\n throw new ForbiddenError('Group chats are off');\n }\n }\n\n private mayMessage(settings: DmSettings, actorTeam: boolean, targetTeam: boolean): boolean {\n if (actorTeam) {\n return settings.teamToAnyone;\n }\n if (targetTeam) {\n return settings.memberToTeam;\n }\n return settings.memberToMember;\n }\n\n private async requireActive(\n repo: ChatRepository,\n orgId: string,\n chatId: string,\n orgUserId: string,\n ): Promise<void> {\n const users = await repo.participants(orgId, chatId);\n this.assertActive(users, orgUserId);\n }\n\n private assertActive(users: ChatUser[], orgUserId: string): void {\n if (!users.some((user) => user.orgUserId === orgUserId && user.leftAt === null)) {\n throw new ForbiddenError('You are not in this chat');\n }\n }\n\n private async requireOwnedImages(\n orgId: string,\n orgUserId: string,\n assetIds: string[],\n ): Promise<void> {\n for (const assetId of assetIds) {\n if (!(await this.images.ownsImage(orgId, assetId, orgUserId))) {\n this.logger.debug('attachment refused', { orgId, orgUserId, assetId });\n throw new NotFoundError('Asset', assetId);\n }\n }\n }\n\n private async requireWritableChat(actor: Actor, chatId: string): Promise<void> {\n const chat = await this.mustChat(this.repo, actor.orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, actor.orgId, chat.id, actor.id);\n if (chat.lockedAt || chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n\n private pendingCreator(chat: Chat, actor: Actor): boolean {\n return chat.kind === 'direct' && chat.status === 'pending' && chat.createdBy === actor.id;\n }\n\n private async mustChat(repo: ChatRepository, orgId: string, chatId: string): Promise<Chat> {\n const chat = await repo.findChat(orgId, chatId);\n if (!chat) {\n throw new NotFoundError('Chat', chatId);\n }\n return chat;\n }\n\n private async mustMessage(\n repo: ChatRepository,\n orgId: string,\n messageId: string,\n ): Promise<Message> {\n const message = await repo.findMessage(orgId, messageId);\n if (!message) {\n throw new NotFoundError('Message', messageId);\n }\n return message;\n }\n\n private async mustUpdate(\n chats: ChatRepository,\n orgId: string,\n chatId: string,\n patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>,\n ): Promise<Chat> {\n const next = await chats.updateChat(orgId, chatId, patch);\n if (!next) {\n throw new NotFoundError('Chat', chatId);\n }\n return next;\n }\n\n private memberRow(\n orgId: string,\n communityId: string,\n chatId: string,\n orgUserId: string,\n now: Date,\n ): ChatUser {\n return {\n orgId,\n communityId,\n chatId,\n orgUserId,\n joinedAt: now,\n leftAt: null,\n lastReadMessageId: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n private newMessage(\n orgId: string,\n chat: Chat,\n actor: Actor,\n input: SendMessageInput,\n now: Date,\n ): Message {\n return {\n orgId,\n id: genId('message'),\n communityId: chat.communityId,\n chatId: chat.id,\n orgUserId: actor.id,\n body: input.body,\n replyToId: input.replyToId,\n clientId: input.clientId ?? null,\n editedAt: null,\n deletedAt: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n private activeIds(users: ChatUser[]): string[] {\n return users\n .filter((user) => user.leftAt === null)\n .map((user) => user.orgUserId)\n .sort();\n }\n\n private chatData(chat: Chat, users: ChatUser[]) {\n return { ...chat, participantIds: this.activeIds(users) };\n }\n\n private meta(actor: Actor) {\n return { actorId: actor.id };\n }\n\n private createdEvent(orgId: string, actor: Actor, chat: Chat, users: ChatUser[]) {\n return discussionEvents.chatCreated.make({\n orgId,\n data: this.chatData(chat, users),\n metadata: this.meta(actor),\n });\n }\n\n private updatedEvent(orgId: string, actor: Actor, chat: Chat, users: ChatUser[]) {\n return discussionEvents.chatUpdated.make({\n orgId,\n data: this.chatData(chat, users),\n metadata: this.meta(actor),\n });\n }\n\n private addedEvent(orgId: string, actor: Actor, chat: Chat, orgUserId: string) {\n return discussionEvents.chatParticipantAdded.make({\n orgId,\n data: {\n chatId: chat.id,\n communityId: chat.communityId,\n orgUserId,\n addedBy: actor.id,\n },\n metadata: this.meta(actor),\n });\n }\n\n private sentEvent(\n orgId: string,\n actor: Actor,\n chat: Chat,\n message: Message,\n mentionedOrgUserIds: string[],\n ) {\n return discussionEvents.messageSent.make({\n orgId,\n data: { ...eventMessage(message), mentionedOrgUserIds },\n metadata: { ...this.meta(actor), chatStatus: chat.status },\n });\n }\n\n private async viewOf(actor: Actor, message: Message): Promise<MessageView> {\n const [view] = await this.withSenders(actor.orgId, actor, [message]);\n return view!;\n }\n\n private async withSenders(\n orgId: string,\n viewer: Actor | null,\n messages: Message[],\n ): Promise<MessageView[]> {\n if (messages.length === 0) {\n return [];\n }\n const messageIds = messages.map((message) => message.id);\n const senderIds = [...new Set(messages.map((message) => message.orgUserId))];\n const [records, permissions, attached, reactions, mentioned] = await Promise.all([\n this.profiles.authorsOf(orgId, senderIds),\n this.authors.permissionsOfMany(orgId, senderIds),\n this.repo.attachmentsOf(orgId, messageIds),\n this.repo.reactionsOf(orgId, messageIds, viewer?.id ?? ''),\n mentionViews(this.mentions, this.profiles, orgId, 'message', messageIds),\n ]);\n const hidden = (message: Message) =>\n message.deletedAt !== null &&\n !viewer?.permissions.has('communities:moderate', message.communityId);\n const shown = (message: Message) => (hidden(message) ? [] : (attached[message.id] ?? []));\n return messages.map((message) => {\n const record = records[message.orgUserId];\n if (!record) {\n throw new NotFoundError('Member', message.orgUserId);\n }\n const counts = reactions[message.id];\n return {\n ...(hidden(message) ? { ...message, body: '' } : message),\n sender: authorView(\n orgId,\n record,\n this.fileUrls,\n teamBadge(permissions.get(message.orgUserId), message.communityId),\n ),\n attachments: shown(message).map((assetId) => ({\n assetId,\n url: this.fileUrls.messageAttachment(message.chatId, message.id, assetId),\n })),\n reactions: counts?.reactions ?? {},\n viewerReaction: counts?.viewerReaction ?? null,\n mentions: hidden(message) ? [] : (mentioned[message.id] ?? []),\n };\n });\n }\n\n private async listItems(\n actor: Actor,\n communityId: string,\n rows: ChatInboxRow[],\n ): Promise<ChatListItem[]> {\n const orgId = actor.orgId;\n const seesDeleted = actor.permissions.has('communities:moderate', communityId);\n const now = new Date();\n const [subscribed, profiles] = await Promise.all([\n this.subscriptions.forViewer(\n orgId,\n actor.id,\n 'chat',\n rows.map((item) => item.chat.id),\n ),\n this.participantsOf(\n orgId,\n communityId,\n rows.flatMap((item) => item.participantIds),\n ),\n ]);\n const subscriptionOf = new Map(subscribed.map((row) => [row.itemId, row]));\n return rows.map((item) => ({\n ...item,\n participants: item.participantIds.flatMap((id) => profiles.get(id) ?? []),\n subscription: activeSubscription(subscriptionOf.get(item.chat.id), now),\n lastMessage:\n item.lastMessage && item.lastMessage.deletedAt !== null && !seesDeleted\n ? { ...item.lastMessage, body: '' }\n : item.lastMessage,\n }));\n }\n\n private async participantsOf(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<Map<string, CommentAuthor>> {\n const ids = [...new Set(orgUserIds)];\n if (ids.length === 0) {\n return new Map();\n }\n const [records, permissions] = await Promise.all([\n this.profiles.authorsOf(orgId, ids),\n this.authors.permissionsOfMany(orgId, ids),\n ]);\n return new Map(\n ids.flatMap((id) => {\n const record = records[id];\n return record\n ? [\n [\n id,\n authorView(\n orgId,\n record,\n this.fileUrls,\n teamBadge(permissions.get(id), communityId),\n ),\n ] as const,\n ]\n : [];\n }),\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { Logger } from "../types/ports.js";
2
+ import { DigestSchedule } from "../types/schemas/content.js";
3
+ import "../shared/ports.js";
4
+ import "../content/index.js";
5
+ import { DigestRunRepository, DigestScheduleReader, DigestService, DigestUnitOfWork } from "./ports.js";
6
+ //#region src/discussion/digests.d.ts
7
+ declare function latestDueAt(schedule: DigestSchedule, now: Date): Date;
8
+ type DigestServiceParams = {
9
+ schedules: DigestScheduleReader;
10
+ runs: Pick<DigestRunRepository, 'listRuns'>;
11
+ uow: DigestUnitOfWork;
12
+ logger?: Logger;
13
+ };
14
+ declare class DigestServiceImpl implements DigestService {
15
+ private readonly schedules;
16
+ private readonly runs;
17
+ private readonly uow;
18
+ private readonly logger;
19
+ constructor(params: DigestServiceParams);
20
+ openDue(now: Date): Promise<void>;
21
+ }
22
+ //#endregion
23
+ export { DigestServiceImpl, DigestServiceParams, latestDueAt };
24
+ //# sourceMappingURL=digests.d.ts.map
@@ -0,0 +1,104 @@
1
+ import { noopLogger } from "../shared/logger.js";
2
+ import { discussionEvents } from "./events.js";
3
+ //#region src/discussion/digests.ts
4
+ const HOUR_MS = 3600 * 1e3;
5
+ function wallTimeIn(at, timeZone) {
6
+ const parts = new Intl.DateTimeFormat("en-US", {
7
+ timeZone,
8
+ hourCycle: "h23",
9
+ year: "numeric",
10
+ month: "numeric",
11
+ day: "numeric",
12
+ hour: "numeric",
13
+ minute: "numeric",
14
+ second: "numeric"
15
+ }).formatToParts(at);
16
+ const part = (type) => Number(parts.find((entry) => entry.type === type)?.value);
17
+ return {
18
+ year: part("year"),
19
+ month: part("month"),
20
+ day: part("day"),
21
+ hour: part("hour"),
22
+ minute: part("minute"),
23
+ second: part("second")
24
+ };
25
+ }
26
+ function offsetAt(at, timeZone) {
27
+ const wall = wallTimeIn(new Date(at), timeZone);
28
+ return Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute, wall.second) - Math.floor(at / 1e3) * 1e3;
29
+ }
30
+ function instantOf(timeZone, year, month, day, hour) {
31
+ const wall = Date.UTC(year, month - 1, day, hour);
32
+ const guess = wall - offsetAt(wall, timeZone);
33
+ return new Date(wall - offsetAt(guess, timeZone));
34
+ }
35
+ function latestDueAt(schedule, now) {
36
+ const today = wallTimeIn(now, schedule.timeZone);
37
+ const weekday = new Date(Date.UTC(today.year, today.month - 1, today.day)).getUTCDay();
38
+ const weekly = schedule.cadence === "weekly";
39
+ const back = weekly ? (weekday - schedule.weekday + 7) % 7 : 0;
40
+ const slot = (daysBack) => instantOf(schedule.timeZone, today.year, today.month, today.day - daysBack, schedule.hour);
41
+ const candidate = slot(back);
42
+ return candidate.getTime() <= now.getTime() ? candidate : slot(back + (weekly ? 7 : 1));
43
+ }
44
+ function runKey(orgId, communityId) {
45
+ return `${orgId}\0${communityId}`;
46
+ }
47
+ var DigestServiceImpl = class {
48
+ schedules;
49
+ runs;
50
+ uow;
51
+ logger;
52
+ constructor(params) {
53
+ this.schedules = params.schedules;
54
+ this.runs = params.runs;
55
+ this.uow = params.uow;
56
+ this.logger = params.logger ?? noopLogger;
57
+ }
58
+ async openDue(now) {
59
+ const [communities, runs] = await Promise.all([this.schedules.communitiesWithDigest(), this.runs.listRuns()]);
60
+ const handled = new Map(runs.map((run) => [runKey(run.orgId, run.communityId), run.lastDueAt]));
61
+ let due = 0;
62
+ let moved = 0;
63
+ let failed = 0;
64
+ for (const { orgId, communityId, digest } of communities) {
65
+ const dueAt = latestDueAt(digest, now);
66
+ const previous = handled.get(runKey(orgId, communityId)) ?? null;
67
+ if (previous !== null && dueAt.getTime() <= previous.getTime()) continue;
68
+ const emits = previous !== null && now.getTime() - dueAt.getTime() <= HOUR_MS;
69
+ try {
70
+ const advanced = await this.uow.run(async ({ digests, outbox }) => {
71
+ if (!await digests.advance(orgId, communityId, dueAt)) return false;
72
+ if (emits && previous !== null) await outbox.append([discussionEvents.digestDue.make({
73
+ orgId,
74
+ data: {
75
+ communityId,
76
+ from: previous,
77
+ to: dueAt
78
+ }
79
+ })]);
80
+ return true;
81
+ });
82
+ if (advanced && emits) due += 1;
83
+ else if (advanced) moved += 1;
84
+ } catch (err) {
85
+ failed += 1;
86
+ this.logger.error("digest not opened", {
87
+ orgId,
88
+ communityId,
89
+ err
90
+ });
91
+ }
92
+ }
93
+ if (due > 0 || moved > 0) this.logger.info("digests opened", {
94
+ due,
95
+ moved,
96
+ failed
97
+ });
98
+ if (failed > 0) throw new Error(`${failed} community digests could not be opened`);
99
+ }
100
+ };
101
+ //#endregion
102
+ export { DigestServiceImpl, latestDueAt };
103
+
104
+ //# sourceMappingURL=digests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digests.js","names":[],"sources":["../../src/discussion/digests.ts"],"sourcesContent":["import type { DigestSchedule } from '../content/index.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { discussionEvents } from './events.js';\nimport type {\n DigestRunRepository,\n DigestScheduleReader,\n DigestService,\n DigestUnitOfWork,\n} from './ports.js';\n\nconst HOUR_MS = 60 * 60 * 1000;\n\ninterface WallTime {\n year: number;\n month: number;\n day: number;\n hour: number;\n minute: number;\n second: number;\n}\n\nfunction wallTimeIn(at: Date, timeZone: string): WallTime {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone,\n hourCycle: 'h23',\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric',\n }).formatToParts(at);\n const part = (type: Intl.DateTimeFormatPartTypes) =>\n Number(parts.find((entry) => entry.type === type)?.value);\n return {\n year: part('year'),\n month: part('month'),\n day: part('day'),\n hour: part('hour'),\n minute: part('minute'),\n second: part('second'),\n };\n}\n\nfunction offsetAt(at: number, timeZone: string): number {\n const wall = wallTimeIn(new Date(at), timeZone);\n const asUtc = Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute, wall.second);\n return asUtc - Math.floor(at / 1000) * 1000;\n}\n\nfunction instantOf(timeZone: string, year: number, month: number, day: number, hour: number): Date {\n const wall = Date.UTC(year, month - 1, day, hour);\n const guess = wall - offsetAt(wall, timeZone);\n return new Date(wall - offsetAt(guess, timeZone));\n}\n\nexport function latestDueAt(schedule: DigestSchedule, now: Date): Date {\n const today = wallTimeIn(now, schedule.timeZone);\n const weekday = new Date(Date.UTC(today.year, today.month - 1, today.day)).getUTCDay();\n const weekly = schedule.cadence === 'weekly';\n const back = weekly ? (weekday - schedule.weekday + 7) % 7 : 0;\n const slot = (daysBack: number) =>\n instantOf(schedule.timeZone, today.year, today.month, today.day - daysBack, schedule.hour);\n const candidate = slot(back);\n return candidate.getTime() <= now.getTime() ? candidate : slot(back + (weekly ? 7 : 1));\n}\n\nfunction runKey(orgId: string, communityId: string): string {\n return `${orgId}\\0${communityId}`;\n}\n\nexport type DigestServiceParams = {\n schedules: DigestScheduleReader;\n runs: Pick<DigestRunRepository, 'listRuns'>;\n uow: DigestUnitOfWork;\n logger?: Logger;\n};\n\nexport class DigestServiceImpl implements DigestService {\n private readonly schedules: DigestScheduleReader;\n private readonly runs: Pick<DigestRunRepository, 'listRuns'>;\n private readonly uow: DigestUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: DigestServiceParams) {\n this.schedules = params.schedules;\n this.runs = params.runs;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n async openDue(now: Date): Promise<void> {\n const [communities, runs] = await Promise.all([\n this.schedules.communitiesWithDigest(),\n this.runs.listRuns(),\n ]);\n const handled = new Map(runs.map((run) => [runKey(run.orgId, run.communityId), run.lastDueAt]));\n let due = 0;\n let moved = 0;\n let failed = 0;\n for (const { orgId, communityId, digest } of communities) {\n const dueAt = latestDueAt(digest, now);\n const previous = handled.get(runKey(orgId, communityId)) ?? null;\n if (previous !== null && dueAt.getTime() <= previous.getTime()) {\n continue;\n }\n const emits = previous !== null && now.getTime() - dueAt.getTime() <= HOUR_MS;\n try {\n const advanced = await this.uow.run(async ({ digests, outbox }) => {\n if (!(await digests.advance(orgId, communityId, dueAt))) {\n return false;\n }\n if (emits && previous !== null) {\n await outbox.append([\n discussionEvents.digestDue.make({\n orgId,\n data: { communityId, from: previous, to: dueAt },\n }),\n ]);\n }\n return true;\n });\n if (advanced && emits) {\n due += 1;\n } else if (advanced) {\n moved += 1;\n }\n } catch (err) {\n failed += 1;\n this.logger.error('digest not opened', { orgId, communityId, err });\n }\n }\n if (due > 0 || moved > 0) {\n this.logger.info('digests opened', { due, moved, failed });\n }\n if (failed > 0) {\n throw new Error(`${failed} community digests could not be opened`);\n }\n }\n}\n"],"mappings":";;;AAWA,MAAM,UAAU,OAAU;AAW1B,SAAS,WAAW,IAAU,UAA4B;CACxD,MAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;EAC7C;EACA,WAAW;EACX,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;EACR,QAAQ;CACV,CAAC,CAAC,CAAC,cAAc,EAAE;CACnB,MAAM,QAAQ,SACZ,OAAO,MAAM,MAAM,UAAU,MAAM,SAAS,IAAI,CAAC,EAAE,KAAK;CAC1D,OAAO;EACL,MAAM,KAAK,MAAM;EACjB,OAAO,KAAK,OAAO;EACnB,KAAK,KAAK,KAAK;EACf,MAAM,KAAK,MAAM;EACjB,QAAQ,KAAK,QAAQ;EACrB,QAAQ,KAAK,QAAQ;CACvB;AACF;AAEA,SAAS,SAAS,IAAY,UAA0B;CACtD,MAAM,OAAO,WAAW,IAAI,KAAK,EAAE,GAAG,QAAQ;CAE9C,OADc,KAAK,IAAI,KAAK,MAAM,KAAK,QAAQ,GAAG,KAAK,KAAK,KAAK,MAAM,KAAK,QAAQ,KAAK,MAC9E,IAAI,KAAK,MAAM,KAAK,GAAI,IAAI;AACzC;AAEA,SAAS,UAAU,UAAkB,MAAc,OAAe,KAAa,MAAoB;CACjG,MAAM,OAAO,KAAK,IAAI,MAAM,QAAQ,GAAG,KAAK,IAAI;CAChD,MAAM,QAAQ,OAAO,SAAS,MAAM,QAAQ;CAC5C,OAAO,IAAI,KAAK,OAAO,SAAS,OAAO,QAAQ,CAAC;AAClD;AAEA,SAAgB,YAAY,UAA0B,KAAiB;CACrE,MAAM,QAAQ,WAAW,KAAK,SAAS,QAAQ;CAC/C,MAAM,UAAU,IAAI,KAAK,KAAK,IAAI,MAAM,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,UAAU;CACrF,MAAM,SAAS,SAAS,YAAY;CACpC,MAAM,OAAO,UAAU,UAAU,SAAS,UAAU,KAAK,IAAI;CAC7D,MAAM,QAAQ,aACZ,UAAU,SAAS,UAAU,MAAM,MAAM,MAAM,OAAO,MAAM,MAAM,UAAU,SAAS,IAAI;CAC3F,MAAM,YAAY,KAAK,IAAI;CAC3B,OAAO,UAAU,QAAQ,KAAK,IAAI,QAAQ,IAAI,YAAY,KAAK,QAAQ,SAAS,IAAI,EAAE;AACxF;AAEA,SAAS,OAAO,OAAe,aAA6B;CAC1D,OAAO,GAAG,MAAM,IAAI;AACtB;AASA,IAAa,oBAAb,MAAwD;CACtD;CACA;CACA;CACA;CAEA,YAAY,QAA6B;EACvC,KAAK,YAAY,OAAO;EACxB,KAAK,OAAO,OAAO;EACnB,KAAK,MAAM,OAAO;EAClB,KAAK,SAAS,OAAO,UAAU;CACjC;CAEA,MAAM,QAAQ,KAA0B;EACtC,MAAM,CAAC,aAAa,QAAQ,MAAM,QAAQ,IAAI,CAC5C,KAAK,UAAU,sBAAsB,GACrC,KAAK,KAAK,SAAS,CACrB,CAAC;EACD,MAAM,UAAU,IAAI,IAAI,KAAK,KAAK,QAAQ,CAAC,OAAO,IAAI,OAAO,IAAI,WAAW,GAAG,IAAI,SAAS,CAAC,CAAC;EAC9F,IAAI,MAAM;EACV,IAAI,QAAQ;EACZ,IAAI,SAAS;EACb,KAAK,MAAM,EAAE,OAAO,aAAa,YAAY,aAAa;GACxD,MAAM,QAAQ,YAAY,QAAQ,GAAG;GACrC,MAAM,WAAW,QAAQ,IAAI,OAAO,OAAO,WAAW,CAAC,KAAK;GAC5D,IAAI,aAAa,QAAQ,MAAM,QAAQ,KAAK,SAAS,QAAQ,GAC3D;GAEF,MAAM,QAAQ,aAAa,QAAQ,IAAI,QAAQ,IAAI,MAAM,QAAQ,KAAK;GACtE,IAAI;IACF,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI,OAAO,EAAE,SAAS,aAAa;KACjE,IAAI,CAAE,MAAM,QAAQ,QAAQ,OAAO,aAAa,KAAK,GACnD,OAAO;KAET,IAAI,SAAS,aAAa,MACxB,MAAM,OAAO,OAAO,CAClB,iBAAiB,UAAU,KAAK;MAC9B;MACA,MAAM;OAAE;OAAa,MAAM;OAAU,IAAI;MAAM;KACjD,CAAC,CACH,CAAC;KAEH,OAAO;IACT,CAAC;IACD,IAAI,YAAY,OACd,OAAO;SACF,IAAI,UACT,SAAS;GAEb,SAAS,KAAK;IACZ,UAAU;IACV,KAAK,OAAO,MAAM,qBAAqB;KAAE;KAAO;KAAa;IAAI,CAAC;GACpE;EACF;EACA,IAAI,MAAM,KAAK,QAAQ,GACrB,KAAK,OAAO,KAAK,kBAAkB;GAAE;GAAK;GAAO;EAAO,CAAC;EAE3D,IAAI,SAAS,GACX,MAAM,IAAI,MAAM,GAAG,OAAO,uCAAuC;CAErE;AACF"}