@headless-lms/core 0.3.0 → 0.4.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 (205) hide show
  1. package/dist/assets/events.d.ts +9 -9
  2. package/dist/assets/index.d.ts +3 -2
  3. package/dist/assets/index.js +2 -1
  4. package/dist/assets/jobs.d.ts +15 -0
  5. package/dist/assets/jobs.js +30 -0
  6. package/dist/assets/jobs.js.map +1 -0
  7. package/dist/assets/ports.d.ts +4 -3
  8. package/dist/assets/service.d.ts +0 -1
  9. package/dist/assets/service.js +0 -4
  10. package/dist/assets/service.js.map +1 -1
  11. package/dist/automations/actions.js +2 -63
  12. package/dist/automations/actions.js.map +1 -1
  13. package/dist/automations/catalog.d.ts +1 -2
  14. package/dist/automations/catalog.js +69 -62
  15. package/dist/automations/catalog.js.map +1 -1
  16. package/dist/automations/community-derivations.js +4 -4
  17. package/dist/automations/community-derivations.js.map +1 -1
  18. package/dist/automations/community-mail.d.ts +3 -17
  19. package/dist/automations/community-mail.js +1 -21
  20. package/dist/automations/community-mail.js.map +1 -1
  21. package/dist/automations/events.d.ts +0 -10
  22. package/dist/automations/index.d.ts +11 -4
  23. package/dist/automations/index.js +8 -1
  24. package/dist/automations/notifications/debounce.d.ts +25 -0
  25. package/dist/automations/notifications/debounce.js +93 -0
  26. package/dist/automations/notifications/debounce.js.map +1 -0
  27. package/dist/automations/notifications/defaults.d.ts +8 -0
  28. package/dist/automations/notifications/defaults.js +316 -0
  29. package/dist/automations/notifications/defaults.js.map +1 -0
  30. package/dist/automations/notifications/delivery.d.ts +28 -0
  31. package/dist/automations/notifications/delivery.js +88 -0
  32. package/dist/automations/notifications/delivery.js.map +1 -0
  33. package/dist/automations/notifications/processor.d.ts +64 -0
  34. package/dist/automations/notifications/processor.js +270 -0
  35. package/dist/automations/notifications/processor.js.map +1 -0
  36. package/dist/automations/notifications/rules/content.d.ts +6 -0
  37. package/dist/automations/notifications/rules/content.js +39 -0
  38. package/dist/automations/notifications/rules/content.js.map +1 -0
  39. package/dist/automations/notifications/rules/discussion.d.ts +7 -0
  40. package/dist/automations/notifications/rules/discussion.js +678 -0
  41. package/dist/automations/notifications/rules/discussion.js.map +1 -0
  42. package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
  43. package/dist/automations/notifications/rules/entitlements.js +249 -0
  44. package/dist/automations/notifications/rules/entitlements.js.map +1 -0
  45. package/dist/automations/notifications/rules/index.d.ts +6 -0
  46. package/dist/automations/notifications/rules/index.js +15 -0
  47. package/dist/automations/notifications/rules/index.js.map +1 -0
  48. package/dist/automations/notifications/rules/organizations.d.ts +6 -0
  49. package/dist/automations/notifications/rules/organizations.js +28 -0
  50. package/dist/automations/notifications/rules/organizations.js.map +1 -0
  51. package/dist/automations/notifications/rules/recipients.d.ts +32 -0
  52. package/dist/automations/notifications/rules/recipients.js +99 -0
  53. package/dist/automations/notifications/rules/recipients.js.map +1 -0
  54. package/dist/automations/notifications/service.d.ts +27 -0
  55. package/dist/automations/notifications/service.js +45 -0
  56. package/dist/automations/notifications/service.js.map +1 -0
  57. package/dist/automations/notifications/types.d.ts +7 -0
  58. package/dist/automations/notifications/types.js +473 -0
  59. package/dist/automations/notifications/types.js.map +1 -0
  60. package/dist/automations/ports.d.ts +119 -5
  61. package/dist/automations/service.d.ts +10 -13
  62. package/dist/automations/service.js +21 -83
  63. package/dist/automations/service.js.map +1 -1
  64. package/dist/content/events.d.ts +130 -80
  65. package/dist/content/events.js +11 -0
  66. package/dist/content/events.js.map +1 -1
  67. package/dist/content/index.d.ts +6 -4
  68. package/dist/content/index.js +3 -1
  69. package/dist/content/model.d.ts +2 -2
  70. package/dist/content/notification-categories.d.ts +36 -0
  71. package/dist/content/notification-categories.js +64 -0
  72. package/dist/content/notification-categories.js.map +1 -0
  73. package/dist/content/ports.d.ts +25 -6
  74. package/dist/content/service.d.ts +4 -4
  75. package/dist/content/service.js +24 -8
  76. package/dist/content/service.js.map +1 -1
  77. package/dist/content/titles.d.ts +22 -0
  78. package/dist/content/titles.js +69 -0
  79. package/dist/content/titles.js.map +1 -0
  80. package/dist/content/types.d.ts +2 -2
  81. package/dist/discussion/chats.d.ts +9 -4
  82. package/dist/discussion/chats.js +80 -47
  83. package/dist/discussion/chats.js.map +1 -1
  84. package/dist/discussion/digests.d.ts +24 -0
  85. package/dist/discussion/digests.js +104 -0
  86. package/dist/discussion/digests.js.map +1 -0
  87. package/dist/discussion/events.d.ts +194 -28
  88. package/dist/discussion/events.js +74 -17
  89. package/dist/discussion/events.js.map +1 -1
  90. package/dist/discussion/index.d.ts +7 -2
  91. package/dist/discussion/index.js +6 -1
  92. package/dist/discussion/jobs.d.ts +16 -0
  93. package/dist/discussion/jobs.js +40 -0
  94. package/dist/discussion/jobs.js.map +1 -0
  95. package/dist/discussion/mentions.d.ts +11 -0
  96. package/dist/discussion/mentions.js +35 -0
  97. package/dist/discussion/mentions.js.map +1 -0
  98. package/dist/discussion/ports.d.ts +107 -24
  99. package/dist/discussion/ports.js.map +1 -1
  100. package/dist/discussion/posts.d.ts +18 -8
  101. package/dist/discussion/posts.js +207 -155
  102. package/dist/discussion/posts.js.map +1 -1
  103. package/dist/discussion/service.d.ts +17 -8
  104. package/dist/discussion/service.js +105 -41
  105. package/dist/discussion/service.js.map +1 -1
  106. package/dist/discussion/subscriptions.d.ts +37 -0
  107. package/dist/discussion/subscriptions.js +99 -0
  108. package/dist/discussion/subscriptions.js.map +1 -0
  109. package/dist/discussion/titles.d.ts +26 -0
  110. package/dist/discussion/titles.js +84 -0
  111. package/dist/discussion/titles.js.map +1 -0
  112. package/dist/entitlements/events.d.ts +20 -20
  113. package/dist/entitlements/index.d.ts +2 -1
  114. package/dist/entitlements/index.js +2 -1
  115. package/dist/entitlements/jobs.d.ts +12 -0
  116. package/dist/entitlements/jobs.js +18 -0
  117. package/dist/entitlements/jobs.js.map +1 -0
  118. package/dist/entitlements/ports.d.ts +7 -3
  119. package/dist/entitlements/service.d.ts +5 -2
  120. package/dist/entitlements/service.js +23 -3
  121. package/dist/entitlements/service.js.map +1 -1
  122. package/dist/identity/devices.d.ts +30 -0
  123. package/dist/identity/devices.js +107 -0
  124. package/dist/identity/devices.js.map +1 -0
  125. package/dist/identity/events.d.ts +2 -2
  126. package/dist/identity/index.d.ts +5 -4
  127. package/dist/identity/index.js +3 -2
  128. package/dist/identity/model.d.ts +2 -2
  129. package/dist/identity/notification-settings.d.ts +28 -0
  130. package/dist/identity/notification-settings.js +113 -0
  131. package/dist/identity/notification-settings.js.map +1 -0
  132. package/dist/identity/ports.d.ts +78 -54
  133. package/dist/integrations/events.d.ts +3 -3
  134. package/dist/organizations/events.d.ts +32 -32
  135. package/dist/organizations/index.d.ts +2 -1
  136. package/dist/organizations/index.js +2 -1
  137. package/dist/organizations/notification-categories.d.ts +32 -0
  138. package/dist/organizations/notification-categories.js +51 -0
  139. package/dist/organizations/notification-categories.js.map +1 -0
  140. package/dist/organizations/ports.d.ts +3 -0
  141. package/dist/organizations/service.d.ts +2 -0
  142. package/dist/organizations/service.js +7 -0
  143. package/dist/organizations/service.js.map +1 -1
  144. package/dist/progress/events.d.ts +14 -14
  145. package/dist/reporting/member/index.d.ts +2 -2
  146. package/dist/reporting/member/model.d.ts +56 -23
  147. package/dist/reporting/member/ports.d.ts +9 -6
  148. package/dist/reporting/member/service.d.ts +33 -14
  149. package/dist/reporting/member/service.js +138 -60
  150. package/dist/reporting/member/service.js.map +1 -1
  151. package/dist/shared/errors.d.ts +13 -1
  152. package/dist/shared/errors.js +18 -1
  153. package/dist/shared/errors.js.map +1 -1
  154. package/dist/shared/id.d.ts +3 -0
  155. package/dist/shared/id.js +4 -1
  156. package/dist/shared/id.js.map +1 -1
  157. package/dist/shared/mailer.d.ts +11 -1
  158. package/dist/shared/mailer.js +32 -5
  159. package/dist/shared/mailer.js.map +1 -1
  160. package/dist/shared/notification-categories.d.ts +16 -0
  161. package/dist/shared/notification-categories.js +94 -0
  162. package/dist/shared/notification-categories.js.map +1 -0
  163. package/dist/shared/notification-settings.d.ts +14 -0
  164. package/dist/shared/notification-settings.js +61 -0
  165. package/dist/shared/notification-settings.js.map +1 -0
  166. package/dist/shared/ports.d.ts +48 -2
  167. package/dist/shared/settings.d.ts +2 -1
  168. package/dist/shared/settings.js +1 -0
  169. package/dist/shared/settings.js.map +1 -1
  170. package/dist/types/automations.d.ts +8 -2
  171. package/dist/types/content.d.ts +2 -2
  172. package/dist/types/discussion.d.ts +2 -2
  173. package/dist/types/email-templates.d.ts +97 -3
  174. package/dist/types/identity.d.ts +2 -2
  175. package/dist/types/index.d.ts +7 -6
  176. package/dist/types/notifications.d.ts +157 -0
  177. package/dist/types/notifications.js +1 -0
  178. package/dist/types/schemas/automations.d.ts +1 -10
  179. package/dist/types/schemas/automations.js +1 -3
  180. package/dist/types/schemas/automations.js.map +1 -1
  181. package/dist/types/schemas/content.d.ts +62 -1
  182. package/dist/types/schemas/content.js +25 -3
  183. package/dist/types/schemas/content.js.map +1 -1
  184. package/dist/types/schemas/discussion.d.ts +85 -12
  185. package/dist/types/schemas/discussion.js +47 -13
  186. package/dist/types/schemas/discussion.js.map +1 -1
  187. package/dist/types/schemas/identity.d.ts +1 -85
  188. package/dist/types/schemas/identity.js +1 -42
  189. package/dist/types/schemas/identity.js.map +1 -1
  190. package/dist/types/schemas/index.d.ts +6 -5
  191. package/dist/types/schemas/index.js +6 -5
  192. package/dist/types/schemas/notifications.d.ts +95 -0
  193. package/dist/types/schemas/notifications.js +56 -0
  194. package/dist/types/schemas/notifications.js.map +1 -0
  195. package/dist/types/schemas/progress.d.ts +3 -3
  196. package/package.json +1 -1
  197. package/dist/automations/community-notifications.d.ts +0 -16
  198. package/dist/automations/community-notifications.js +0 -84
  199. package/dist/automations/community-notifications.js.map +0 -1
  200. package/dist/automations/seeds.d.ts +0 -17
  201. package/dist/automations/seeds.js +0 -235
  202. package/dist/automations/seeds.js.map +0 -1
  203. package/dist/identity/notifications.d.ts +0 -27
  204. package/dist/identity/notifications.js +0 -101
  205. package/dist/identity/notifications.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"posts.js","names":[],"sources":["../../src/discussion/posts.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 { canModerate, isStaffRole } from '../organizations/index.js';\nimport type { Role } from '../organizations/index.js';\nimport type { CommunityReader, SpaceDetail } from '../content/index.js';\nimport type { CommunityAccessReader } from '../entitlements/index.js';\nimport type { AssetFiles, FileUrls, MemberImageReader } from '../assets/index.js';\nimport type { Post, PostFlag, PostFlagResolution, PostStatus, PostView } 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 CreatePostInput,\n FeedQuery,\n PageQuery,\n PostPatch,\n PostCommentReader,\n PostReactions,\n PostRepository,\n PostService,\n PostUnitOfWork,\n SpaceModerationQueue,\n UpdatePostInput,\n} from './ports.js';\nimport { MODERATION_QUEUE_LIMIT } from './ports.js';\nimport type { Page, ReactionEmoji } from './types.js';\n\nexport type PostServiceParams = {\n repo: PostRepository;\n access: CommunityAccessReader;\n content: CommunityReader;\n authors: AuthorRoleReader;\n profiles: AuthorProfileReader;\n comments: PostCommentReader;\n images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n fileUrls: FileUrls;\n uow: PostUnitOfWork;\n logger?: Logger;\n};\n\nconst PUBLISHED_ONLY: PostStatus[] = ['published'];\nconst DUE_BATCH_SIZE = 100;\nconst MODERATOR_VISIBLE: PostStatus[] = ['published', 'pending', 'removed'];\n\nexport class PostServiceImpl implements PostService {\n private readonly repo: PostRepository;\n private readonly access: CommunityAccessReader;\n private readonly content: CommunityReader;\n private readonly authors: AuthorRoleReader;\n private readonly profiles: AuthorProfileReader;\n private readonly comments: PostCommentReader;\n private readonly images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n private readonly fileUrls: FileUrls;\n private readonly uow: PostUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: PostServiceParams) {\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.comments = params.comments;\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 visibleTitles(\n orgId: string,\n actor: Actor,\n postIds: string[],\n ): Promise<Record<string, string>> {\n const found = await this.repo.findMany(orgId, [...new Set(postIds)]);\n const communityIds = [...new Set(found.map((post) => post.communityId))];\n const open = new Set(\n (\n await Promise.all(\n communityIds.map((communityId) => this.access.openSpaceIds(orgId, actor, communityId)),\n )\n ).flat(),\n );\n return Object.fromEntries(\n found\n .filter((post) => open.has(post.spaceId) && this.canSee(actor, post))\n .map((post) => [post.id, post.title]),\n );\n }\n\n async create(orgId: string, actor: Actor, input: CreatePostInput): Promise<PostView> {\n const space = await this.requireOpen(orgId, actor, input.spaceId, input.communityId);\n await this.requireOwnedImage(orgId, actor.id, input.coverAssetId);\n this.assertTopic(space, input.topicId);\n const now = new Date();\n const landing = this.initial(input, space, actor, now);\n const post: Post = {\n orgId,\n id: genId('post'),\n communityId: space.communityId,\n spaceId: space.id,\n orgUserId: actor.id,\n topicId: input.topicId,\n title: input.title,\n body: input.body,\n extra: input.extra,\n coverAssetId: input.coverAssetId,\n pinned: false,\n commentsClosed: null,\n reactionsEnabled: null,\n createdAt: now,\n updatedAt: now,\n ...landing,\n };\n const saved = await this.uow.run(async ({ posts, outbox }) => {\n const created = await posts.insert(orgId, post);\n const events: NewDiscussionEvent[] = [\n discussionEvents.postCreated.make({\n orgId,\n data: created,\n metadata: this.meta(actor, created.status === 'pending' ? null : undefined),\n }),\n ];\n if (created.status === 'published') {\n events.push(\n discussionEvents.postPublished.make({\n orgId,\n data: created,\n metadata: this.meta(actor, null),\n }),\n );\n }\n await outbox.append(events);\n return created;\n });\n this.logger.info('post created', {\n orgId,\n postId: saved.id,\n status: saved.status,\n actorId: actor.id,\n });\n return this.viewOf(orgId, actor, saved);\n }\n\n async update(\n orgId: string,\n postId: string,\n actor: Actor,\n patch: UpdatePostInput,\n ): Promise<PostView> {\n const { post, space } = await this.loadVisible(orgId, actor, postId);\n this.assertAuthor(actor, post);\n if (post.status === 'removed') {\n throw new ConflictError('This post was removed');\n }\n if (patch.topicId !== undefined) {\n this.assertTopic(space, patch.topicId);\n }\n if (typeof patch.coverAssetId === 'string') {\n await this.requireOwnedImage(orgId, actor.id, patch.coverAssetId);\n }\n if (!this.patchChanges(post, patch)) {\n return this.viewOf(orgId, actor, post);\n }\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, patch);\n await outbox.append([\n discussionEvents.postUpdated.make({\n orgId,\n data: next,\n metadata: this.meta(actor, next.status === 'pending' ? post.status : undefined),\n }),\n ]);\n return next;\n });\n this.logger.info('post updated', {\n orgId,\n postId,\n fields: Object.keys(patch),\n actorId: actor.id,\n });\n return this.viewOf(orgId, actor, updated);\n }\n\n async submit(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n const { post, space } = await this.loadVisible(orgId, actor, postId);\n this.assertAuthor(actor, post);\n if (post.status !== 'draft' && post.status !== 'scheduled') {\n throw new ConflictError('This post cannot be submitted');\n }\n this.assertTopic(space, post.topicId);\n const saved = await this.commitSubmission(orgId, post, space, actor.role, actor, new Date());\n this.logger.info('post submitted', { orgId, postId, status: saved.status, actorId: actor.id });\n return this.viewOf(orgId, actor, saved);\n }\n\n async schedule(\n orgId: string,\n postId: string,\n actor: Actor,\n scheduledAt: Date,\n ): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n this.assertAuthor(actor, post);\n if (post.status === 'published' || post.status === 'removed' || post.status === 'pending') {\n throw new ConflictError('This post cannot be scheduled');\n }\n if (scheduledAt <= new Date()) {\n throw new InvalidInputError('A scheduled time must be in the future');\n }\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, {\n status: 'scheduled',\n scheduledAt,\n publishedAt: null,\n removedBy: null,\n });\n await outbox.append([\n discussionEvents.postUpdated.make({\n orgId,\n data: next,\n metadata: this.meta(actor, post.status),\n }),\n ]);\n return next;\n });\n this.logger.info('post scheduled', {\n orgId,\n postId,\n scheduledAt: scheduledAt.toISOString(),\n actorId: actor.id,\n });\n return this.viewOf(orgId, actor, updated);\n }\n\n async approve(\n orgId: string,\n postId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId, communityId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot review this post');\n }\n if (post.status !== 'pending') {\n throw new ConflictError('This post is not awaiting review');\n }\n const now = new Date();\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, {\n status: 'published',\n publishedAt: now,\n scheduledAt: null,\n removedBy: null,\n });\n await outbox.append([\n discussionEvents.postPublished.make({\n orgId,\n data: next,\n metadata: this.meta(actor, post.status),\n }),\n ]);\n return next;\n });\n this.logger.info('post approved', { orgId, postId, actorId: actor.id });\n return this.viewOf(orgId, actor, updated);\n }\n\n async remove(\n orgId: string,\n postId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId, communityId);\n if (post.orgUserId !== actor.id && !this.canModerate(actor)) {\n throw new ForbiddenError('You cannot remove this post');\n }\n if (post.status === 'removed') {\n throw new ConflictError('This post was removed');\n }\n if (post.status !== 'published' && post.status !== 'pending') {\n throw new ConflictError('This post cannot be removed');\n }\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, {\n status: 'removed',\n removedBy: actor.id,\n });\n if (post.orgUserId !== actor.id) {\n await posts.resolveFlags(orgId, postId, {\n resolvedAt: new Date(),\n resolvedBy: actor.id,\n resolution: 'removed',\n });\n }\n await outbox.append([\n discussionEvents.postRemoved.make({\n orgId,\n data: next,\n metadata: this.meta(actor, post.status),\n }),\n ]);\n return next;\n });\n this.logger.info('post removed', { orgId, postId, actorId: actor.id });\n return this.viewOf(orgId, actor, updated);\n }\n\n async restore(\n orgId: string,\n postId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<PostView> {\n const { post, space } = await this.loadVisible(orgId, actor, postId, communityId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot moderate this post');\n }\n if (post.status !== 'removed') {\n throw new ConflictError('This post was not removed');\n }\n const now = new Date();\n const role = await this.authors.roleOf(orgId, post.orgUserId);\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(\n posts,\n orgId,\n postId,\n this.submissionOf(space.settings.requireReview, role, now),\n );\n const metadata = this.meta(actor, post.status);\n const event =\n next.status === 'published'\n ? discussionEvents.postPublished.make({ orgId, data: next, metadata })\n : discussionEvents.postUpdated.make({ orgId, data: next, metadata });\n await outbox.append([event]);\n return next;\n });\n this.logger.info('post restored', {\n orgId,\n postId,\n status: updated.status,\n actorId: actor.id,\n });\n return this.viewOf(orgId, actor, updated);\n }\n\n pin(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { pinned: true },\n (post) => post.pinned,\n 'post pinned',\n );\n }\n\n unpin(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { pinned: false },\n (post) => !post.pinned,\n 'post unpinned',\n );\n }\n\n closeComments(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { commentsClosed: true },\n (post) => post.commentsClosed === true,\n 'post comments closed',\n );\n }\n\n openComments(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { commentsClosed: false },\n (post) => post.commentsClosed === false,\n 'post comments opened',\n );\n }\n\n setReactions(orgId: string, postId: string, actor: Actor, enabled: boolean): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { reactionsEnabled: enabled },\n (post) => post.reactionsEnabled === enabled,\n 'post reactions changed',\n );\n }\n\n async setReaction(\n orgId: string,\n postId: string,\n actor: Actor,\n emoji: ReactionEmoji | null,\n ): Promise<PostReactions> {\n const { post, space } = await this.loadVisible(orgId, actor, postId);\n if (!(post.reactionsEnabled ?? space.settings.reactions)) {\n throw new ForbiddenError('Reactions are off on this post');\n }\n await this.repo.setReaction(orgId, postId, actor.id, emoji);\n this.logger.debug('post reaction set', { orgId, postId, orgUserId: actor.id });\n const counts = await this.repo.reactionsOf(orgId, [postId], actor.id);\n return counts[postId] ?? { reactions: {} };\n }\n\n async follow(orgId: string, postId: string, actor: Actor): Promise<void> {\n await this.loadVisible(orgId, actor, postId);\n await this.repo.follow(orgId, postId, actor.id);\n this.logger.debug('post followed', { orgId, postId, orgUserId: actor.id });\n }\n\n async unfollow(orgId: string, postId: string, actor: Actor): Promise<void> {\n await this.loadVisible(orgId, actor, postId);\n await this.repo.unfollow(orgId, postId, actor.id);\n this.logger.debug('post unfollowed', { orgId, postId, orgUserId: actor.id });\n }\n\n async bookmark(orgId: string, postId: string, actor: Actor): Promise<void> {\n await this.loadVisible(orgId, actor, postId);\n await this.repo.bookmark(orgId, postId, actor.id);\n this.logger.debug('post bookmarked', { orgId, postId, orgUserId: actor.id });\n }\n\n async unbookmark(orgId: string, postId: string, actor: Actor): Promise<void> {\n await this.loadVisible(orgId, actor, postId);\n await this.repo.unbookmark(orgId, postId, actor.id);\n this.logger.debug('post unbookmarked', { orgId, postId, orgUserId: actor.id });\n }\n\n async flag(orgId: string, postId: string, actor: Actor, reason: string): Promise<PostFlag> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n const now = new Date();\n const flag: PostFlag = {\n orgId,\n id: genId('postFlag'),\n postId,\n orgUserId: actor.id,\n reason,\n resolvedAt: null,\n resolution: null,\n resolvedBy: null,\n createdAt: now,\n updatedAt: now,\n };\n const saved = await this.uow.run(async ({ posts, outbox }) => {\n const created = await posts.insertFlag(orgId, flag);\n if (!created) {\n const existing = await posts.findFlag(orgId, postId, actor.id);\n if (!existing) {\n throw new NotFoundError('Flag', postId);\n }\n return existing;\n }\n await outbox.append([\n discussionEvents.postReported.make({\n orgId,\n data: { ...created, communityId: post.communityId },\n metadata: this.meta(actor),\n }),\n ]);\n return created;\n });\n this.logger.info('post flagged', { orgId, postId, flagId: saved.id, actorId: actor.id });\n return saved;\n }\n\n async resolveFlags(\n orgId: string,\n postId: string,\n actor: Actor,\n resolution: Extract<PostFlagResolution, 'dismissed'>,\n communityId?: string,\n ): Promise<void> {\n await this.loadVisible(orgId, actor, postId, communityId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot work this queue');\n }\n await this.repo.resolveFlags(orgId, postId, {\n resolvedAt: new Date(),\n resolvedBy: actor.id,\n resolution,\n });\n this.logger.info('post flags resolved', { orgId, postId, resolution, actorId: actor.id });\n }\n\n async spaceFeed(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n query: FeedQuery,\n ): Promise<Page<PostView>> {\n const space = await this.requireOpen(orgId, actor, spaceId, communityId);\n if (query.topicId && !space.topics.some((topic) => topic.id === query.topicId)) {\n throw new NotFoundError('Topic', query.topicId);\n }\n const page = await this.feedPage(orgId, actor, [spaceId], query);\n this.logger.debug('space feed read', { orgId, spaceId, rows: page.rows.length });\n return page;\n }\n\n async memberFeed(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n ): Promise<Page<PostView>> {\n const spaceIds = await this.openSpaceIdsIn(orgId, actor, communityId);\n const page = await this.feedPage(orgId, actor, spaceIds, query);\n this.logger.debug('member feed read', {\n orgId,\n communityId,\n spaces: spaceIds.length,\n rows: page.rows.length,\n });\n return page;\n }\n\n private async openSpaceIdsIn(\n orgId: string,\n actor: Actor,\n communityId: string,\n ): Promise<string[]> {\n if (!(await this.access.isCommunityMember(orgId, actor, communityId))) {\n throw new NotFoundError('Community', communityId);\n }\n return this.access.openSpaceIds(orgId, actor, communityId);\n }\n\n async listOwn(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<PostView>> {\n const spaceIds = await this.openSpaceIdsIn(orgId, actor, communityId);\n const page = await this.repo.listByAuthor(orgId, actor.id, spaceIds, query);\n this.logger.debug('own posts read', { orgId, communityId, rows: page.rows.length });\n return { ...page, rows: await this.viewsOf(orgId, actor, page.rows) };\n }\n\n async listBookmarked(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<PostView>> {\n const spaceIds = await this.openSpaceIdsIn(orgId, actor, communityId);\n const page = await this.repo.listBookmarked(orgId, actor.id, spaceIds, {\n ...query,\n visibleStatuses: this.visibleStatuses(actor),\n });\n this.logger.debug('bookmarked posts read', { orgId, communityId, rows: page.rows.length });\n return { ...page, rows: await this.viewsOf(orgId, actor, page.rows) };\n }\n\n async coverFile(orgId: string, actor: Actor, postId: string): Promise<string | null> {\n const post = await this.repo.find(orgId, postId);\n if (!post?.coverAssetId || !this.canSee(actor, post)) {\n return null;\n }\n const space = await this.content.getSpace(orgId, post.spaceId);\n if (!space || !(await this.access.canOpen(orgId, actor, space))) {\n return null;\n }\n return this.images.signFile(orgId, post.coverAssetId);\n }\n\n async getPost(orgId: string, actor: Actor, postId: string): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n this.logger.debug('post read', { orgId, postId });\n return this.viewOf(orgId, actor, post);\n }\n\n async moderationQueue(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceModerationQueue> {\n await this.requireOpen(orgId, actor, spaceId, communityId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot work this queue');\n }\n const [pending, flags, openCommentFlags] = await Promise.all([\n this.repo.listPending(orgId, spaceId, MODERATION_QUEUE_LIMIT),\n this.repo.listOpenFlags(orgId, spaceId, MODERATION_QUEUE_LIMIT),\n this.comments.listOpenFlagsForSpace(orgId, spaceId, MODERATION_QUEUE_LIMIT),\n ]);\n this.logger.debug('space queue read', {\n orgId,\n spaceId,\n pending: pending.length,\n flags: flags.length,\n commentFlags: openCommentFlags.length,\n });\n return {\n pending: await this.viewsOf(orgId, actor, pending),\n flags,\n commentFlags: openCommentFlags,\n };\n }\n\n visible(\n orgId: string,\n actor: Actor,\n postId: string,\n ): Promise<{ post: Post; space: SpaceDetail }> {\n return this.loadVisible(orgId, actor, postId);\n }\n\n find(orgId: string, postId: string): Promise<Post | null> {\n return this.repo.find(orgId, postId);\n }\n\n followerIds(orgId: string, postId: string): Promise<string[]> {\n return this.repo.followerIds(orgId, postId);\n }\n\n hasDue(orgId: string, now: Date): Promise<boolean> {\n return this.repo.hasDue(orgId, now);\n }\n\n async publishDue(orgId: string, now: Date): Promise<void> {\n let afterId: string | undefined;\n let count = 0;\n let refused = 0;\n for (;;) {\n const due = await this.repo.listDue(orgId, now, DUE_BATCH_SIZE, afterId);\n if (due.length === 0) {\n break;\n }\n const batch = await this.submitDue(orgId, now, due);\n count += batch.count;\n refused += batch.refused;\n if (due.length < DUE_BATCH_SIZE) {\n break;\n }\n afterId = due[due.length - 1]!.id;\n }\n if (count > 0) {\n this.logger.info('due posts submitted', { orgId, count, refused });\n } else {\n this.logger.debug('no posts due', { orgId });\n }\n }\n\n private async submitDue(\n orgId: string,\n now: Date,\n due: Post[],\n ): Promise<{ count: number; refused: number }> {\n const spaces = new Map<string, SpaceDetail | null>();\n const roles = new Map<string, Role | null>();\n const refused = new Set<string>();\n for (const post of due) {\n if (!spaces.has(post.spaceId)) {\n spaces.set(post.spaceId, await this.content.getSpace(orgId, post.spaceId));\n }\n if (!roles.has(post.orgUserId)) {\n roles.set(post.orgUserId, await this.authors.roleOf(orgId, post.orgUserId));\n }\n const space = spaces.get(post.spaceId);\n const role = roles.get(post.orgUserId) ?? null;\n if (space && !(await this.submittable(orgId, post, space, role))) {\n refused.add(post.id);\n }\n }\n const count = await this.uow.run(async ({ posts, outbox }) => {\n const events: NewDiscussionEvent[] = [];\n for (const post of due) {\n const current = await posts.find(orgId, post.id);\n if (\n !current ||\n current.status !== 'scheduled' ||\n current.scheduledAt === null ||\n current.scheduledAt > now\n ) {\n continue;\n }\n const space = spaces.get(current.spaceId);\n if (!space) {\n this.logger.error('scheduled post has no space', {\n orgId,\n postId: current.id,\n spaceId: current.spaceId,\n });\n continue;\n }\n const next = await posts.update(\n orgId,\n current.id,\n refused.has(current.id)\n ? { status: 'draft', scheduledAt: null, publishedAt: null, removedBy: null }\n : this.submissionOf(\n space.settings.requireReview,\n roles.get(current.orgUserId) ?? null,\n now,\n ),\n );\n if (!next) {\n continue;\n }\n const metadata = this.meta(null, current.status);\n events.push(\n next.status === 'published'\n ? discussionEvents.postPublished.make({ orgId, data: next, metadata })\n : discussionEvents.postUpdated.make({ orgId, data: next, metadata }),\n );\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n return events.length;\n });\n return { count, refused: refused.size };\n }\n\n private async submittable(\n orgId: string,\n post: Post,\n space: SpaceDetail,\n role: Role | null,\n ): Promise<boolean> {\n if (role === null || !this.topicFits(space, post.topicId)) {\n return false;\n }\n return this.access.canOpen(orgId, { id: post.orgUserId, role }, space);\n }\n\n private async feedPage(\n orgId: string,\n actor: Actor,\n spaceIds: string[],\n query: FeedQuery,\n ): Promise<Page<PostView>> {\n const page = await this.repo.listPublished(orgId, spaceIds, query);\n return { ...page, rows: await this.viewsOf(orgId, actor, page.rows) };\n }\n\n private async viewOf(orgId: string, actor: Actor, post: Post): Promise<PostView> {\n const [view] = await this.viewsOf(orgId, actor, [post]);\n return view!;\n }\n\n private async viewsOf(orgId: string, actor: Actor, posts: Post[]): Promise<PostView[]> {\n if (posts.length === 0) {\n return [];\n }\n const postIds = posts.map((post) => post.id);\n const [records, reactions, commentCounts, followed, bookmarked] = await Promise.all([\n this.profiles.authorsOf(orgId, [...new Set(posts.map((post) => post.orgUserId))]),\n this.repo.reactionsOf(orgId, postIds, actor.id),\n this.comments.publishedCommentCounts(orgId, postIds),\n this.repo.followedAmong(orgId, postIds, actor.id),\n this.repo.bookmarkedAmong(orgId, postIds, actor.id),\n ]);\n const following = new Set(followed);\n const saved = new Set(bookmarked);\n return posts.map((post) => {\n const record = records[post.orgUserId];\n if (!record) {\n throw new NotFoundError('Member', post.orgUserId);\n }\n const counts = reactions[post.id];\n return {\n ...post,\n coverUrl: post.coverAssetId ? this.fileUrls.postCover(post.id) : null,\n author: authorView(orgId, record, this.fileUrls),\n reactions: counts?.reactions ?? {},\n viewerReaction: counts?.viewerReaction ?? null,\n commentCount: commentCounts[post.id] ?? 0,\n following: following.has(post.id),\n bookmarked: saved.has(post.id),\n };\n });\n }\n\n private async commitSubmission(\n orgId: string,\n post: Post,\n space: SpaceDetail,\n role: Role,\n actor: Actor,\n now: Date,\n ): Promise<Post> {\n return this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(\n posts,\n orgId,\n post.id,\n this.submissionOf(space.settings.requireReview, role, now),\n );\n const metadata = this.meta(actor, post.status);\n const event =\n next.status === 'published'\n ? discussionEvents.postPublished.make({ orgId, data: next, metadata })\n : discussionEvents.postUpdated.make({ orgId, data: next, metadata });\n await outbox.append([event]);\n return next;\n });\n }\n\n private async moderate(\n orgId: string,\n postId: string,\n actor: Actor,\n patch: PostPatch,\n unchanged: (post: Post) => boolean,\n log: string,\n ): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot moderate this post');\n }\n if (post.status === 'removed') {\n throw new ConflictError('This post was removed');\n }\n if (unchanged(post)) {\n return this.viewOf(orgId, actor, post);\n }\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, patch);\n await outbox.append([\n discussionEvents.postUpdated.make({\n orgId,\n data: next,\n metadata: this.meta(actor, next.status === 'pending' ? post.status : undefined),\n }),\n ]);\n return next;\n });\n this.logger.info(log, { orgId, postId, actorId: actor.id });\n return this.viewOf(orgId, actor, updated);\n }\n\n private async mustUpdate(\n posts: PostRepository,\n orgId: string,\n postId: string,\n patch: PostPatch,\n ): Promise<Post> {\n const next = await posts.update(orgId, postId, patch);\n if (!next) {\n throw new NotFoundError('Post', postId);\n }\n return next;\n }\n\n private initial(input: CreatePostInput, space: SpaceDetail, actor: Actor, now: Date) {\n if (input.mode === 'draft') {\n return { status: 'draft' as const, scheduledAt: null, publishedAt: null, removedBy: null };\n }\n if (input.mode === 'schedule') {\n if (!input.scheduledAt) {\n throw new InvalidInputError('A scheduled post needs a time');\n }\n if (input.scheduledAt <= now) {\n throw new InvalidInputError('A scheduled time must be in the future');\n }\n return {\n status: 'scheduled' as const,\n scheduledAt: input.scheduledAt,\n publishedAt: null,\n removedBy: null,\n };\n }\n return this.submissionOf(space.settings.requireReview, actor.role, now);\n }\n\n private submissionOf(requireReview: boolean, role: Role | null, now: Date) {\n if (requireReview && !(role && isStaffRole(role))) {\n return { status: 'pending' as const, scheduledAt: null, publishedAt: null, removedBy: null };\n }\n return { status: 'published' as const, scheduledAt: null, publishedAt: now, removedBy: null };\n }\n\n private async requireOwnedImage(\n orgId: string,\n orgUserId: string,\n assetId: string | null,\n ): Promise<void> {\n if (assetId === null) {\n return;\n }\n if (!(await this.images.ownsImage(orgId, assetId, orgUserId))) {\n this.logger.debug('cover refused', { orgId, orgUserId, assetId });\n throw new NotFoundError('Asset', assetId);\n }\n }\n\n private async openSpace(\n orgId: string,\n actor: Actor,\n spaceId: string,\n communityId?: string,\n ): Promise<SpaceDetail | null> {\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space || (communityId !== undefined && space.communityId !== communityId)) {\n return null;\n }\n return (await this.access.canOpen(orgId, actor, space)) ? space : null;\n }\n\n private async requireOpen(\n orgId: string,\n actor: Actor,\n spaceId: string,\n communityId?: string,\n ): Promise<SpaceDetail> {\n const space = await this.openSpace(orgId, actor, spaceId, communityId);\n if (!space) {\n throw new NotFoundError('Space', spaceId);\n }\n return space;\n }\n\n private async loadVisible(orgId: string, actor: Actor, postId: string, communityId?: string) {\n const post = await this.repo.find(orgId, postId);\n if (!post || (communityId !== undefined && post.communityId !== communityId)) {\n throw new NotFoundError('Post', postId);\n }\n const space = await this.openSpace(orgId, actor, post.spaceId);\n if (!space || !this.canSee(actor, post)) {\n throw new NotFoundError('Post', postId);\n }\n return { post, space };\n }\n\n private canSee(actor: Actor, post: Post): boolean {\n return post.orgUserId === actor.id || this.visibleStatuses(actor).includes(post.status);\n }\n\n private visibleStatuses(actor: Actor): PostStatus[] {\n return this.canModerate(actor) ? MODERATOR_VISIBLE : PUBLISHED_ONLY;\n }\n\n private canModerate(actor: Actor): boolean {\n return canModerate(actor.role);\n }\n\n private assertAuthor(actor: Actor, post: Post): void {\n if (post.orgUserId !== actor.id) {\n throw new ForbiddenError('You can change only your own post');\n }\n }\n\n private topicFits(space: SpaceDetail, topicId: string | null): boolean {\n if (topicId === null) {\n return space.topics.length === 0;\n }\n return space.topics.some((topic) => topic.id === topicId);\n }\n\n private assertTopic(space: SpaceDetail, topicId: string | null): void {\n if (this.topicFits(space, topicId)) {\n return;\n }\n if (topicId === null) {\n throw new InvalidInputError('This space requires a topic');\n }\n throw new NotFoundError('Topic', topicId);\n }\n\n private patchChanges(post: Post, patch: UpdatePostInput): boolean {\n return (Object.keys(patch) as (keyof UpdatePostInput)[]).some((key) => {\n const value = patch[key];\n if (value === undefined) {\n return false;\n }\n if (key === 'extra') {\n return JSON.stringify(post.extra) !== JSON.stringify(value);\n }\n return post[key] !== value;\n });\n }\n\n private meta(actor: Actor | null, previousStatus?: PostStatus | null) {\n return {\n ...(actor ? { actorOrgUserId: actor.id } : {}),\n ...(previousStatus !== undefined ? { previousStatus } : {}),\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"posts.js","names":[],"sources":["../../src/discussion/posts.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, Scheduler } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { canModerate, isStaffRole } from '../organizations/index.js';\nimport type { Role } from '../organizations/index.js';\nimport type { CommunityReader, SpaceDetail } from '../content/index.js';\nimport type { CommunityAccessReader } from '../entitlements/index.js';\nimport type { AssetFiles, FileUrls, MemberImageReader } from '../assets/index.js';\nimport type { Post, PostFlag, PostFlagResolution, PostStatus, PostView } 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 { POST_PUBLISH_JOB } from './jobs.js';\nimport { activeSubscription } from './subscriptions.js';\nimport { keepAllowed, mentionTokens, mentionViews } from './mentions.js';\nimport type {\n AuthorProfileReader,\n AuthorRoleReader,\n CreatePostInput,\n FeedQuery,\n MentionsRepository,\n PageQuery,\n PostPatch,\n PostCommentReader,\n PostReactions,\n PostRepository,\n PostService,\n PostUnitOfWork,\n SpaceModerationQueue,\n SubscriptionsRepository,\n UpdatePostInput,\n PublishedPost,\n} from './ports.js';\nimport { MODERATION_QUEUE_LIMIT } from './ports.js';\nimport type { Page, ReactionEmoji } from './types.js';\n\nexport type PostServiceParams = {\n repo: PostRepository;\n access: CommunityAccessReader;\n content: CommunityReader;\n authors: AuthorRoleReader;\n profiles: AuthorProfileReader;\n comments: PostCommentReader;\n subscriptions: SubscriptionsRepository;\n mentions: MentionsRepository;\n images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n fileUrls: FileUrls;\n uow: PostUnitOfWork;\n scheduler: Pick<Scheduler, 'at' | 'cancel'>;\n logger?: Logger;\n};\n\ntype EventMeta = { actorOrgUserId?: string; previousStatus?: PostStatus | null };\n\nconst PUBLISHED_ONLY: PostStatus[] = ['published'];\nconst MODERATOR_VISIBLE: PostStatus[] = ['published', 'pending', 'removed'];\nconst DRAFT = {\n status: 'draft' as const,\n scheduledAt: null,\n publishedAt: null,\n removedBy: null,\n};\n\nexport class PostServiceImpl implements PostService {\n private readonly repo: PostRepository;\n private readonly access: CommunityAccessReader;\n private readonly content: CommunityReader;\n private readonly authors: AuthorRoleReader;\n private readonly profiles: AuthorProfileReader;\n private readonly comments: PostCommentReader;\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: PostUnitOfWork;\n private readonly scheduler: Pick<Scheduler, 'at' | 'cancel'>;\n private readonly logger: Logger;\n\n constructor(params: PostServiceParams) {\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.comments = params.comments;\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.scheduler = params.scheduler;\n this.logger = params.logger ?? noopLogger;\n }\n\n async visibleTitles(\n orgId: string,\n actor: Actor,\n postIds: string[],\n ): Promise<Record<string, string>> {\n const found = await this.repo.findMany(orgId, [...new Set(postIds)]);\n const communityIds = [...new Set(found.map((post) => post.communityId))];\n const open = new Set(\n (\n await Promise.all(\n communityIds.map((communityId) => this.access.openSpaceIds(orgId, actor, communityId)),\n )\n ).flat(),\n );\n return Object.fromEntries(\n found\n .filter((post) => open.has(post.spaceId) && this.canSee(actor, post))\n .map((post) => [post.id, post.title]),\n );\n }\n\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]> {\n return this.repo.publishedBetween(orgId, communityId, from, to);\n }\n\n async create(orgId: string, actor: Actor, input: CreatePostInput): Promise<PostView> {\n const space = await this.requireOpen(orgId, actor, input.spaceId, input.communityId);\n await this.requireOwnedImage(orgId, actor.id, input.coverAssetId);\n this.assertTopic(space, input.topicId);\n const now = new Date();\n const landing = this.initial(input, space, actor, now);\n const post: Post = {\n orgId,\n id: genId('post'),\n communityId: space.communityId,\n spaceId: space.id,\n orgUserId: actor.id,\n topicId: input.topicId,\n title: input.title,\n body: input.body,\n extra: input.extra,\n coverAssetId: input.coverAssetId,\n pinned: false,\n commentsClosed: null,\n reactionsEnabled: null,\n createdAt: now,\n updatedAt: now,\n ...landing,\n };\n const mentioned = await this.mentionable(orgId, space.id, post.title, post.body);\n const saved = await this.uow.run(async ({ posts, mentions, outbox }) => {\n const created = await posts.insert(orgId, post);\n await mentions.sync(orgId, 'post', created.id, mentioned, now);\n const events: NewDiscussionEvent[] = [\n discussionEvents.postCreated.make({\n orgId,\n data: created,\n metadata: this.meta(actor, created.status === 'pending' ? null : undefined),\n }),\n ];\n if (created.status === 'published') {\n events.push(this.published(orgId, created, this.meta(actor, null), mentioned));\n }\n await outbox.append(events);\n return created;\n });\n this.logger.info('post created', {\n orgId,\n postId: saved.id,\n status: saved.status,\n actorId: actor.id,\n });\n if (saved.status === 'scheduled') {\n await this.arm(orgId, actor, saved, null);\n }\n return this.viewOf(orgId, actor, saved);\n }\n\n async update(\n orgId: string,\n postId: string,\n actor: Actor,\n patch: UpdatePostInput,\n ): Promise<PostView> {\n const { post, space } = await this.loadVisible(orgId, actor, postId);\n this.assertAuthor(actor, post);\n if (post.status === 'removed') {\n throw new ConflictError('This post was removed');\n }\n if (patch.topicId !== undefined) {\n this.assertTopic(space, patch.topicId);\n }\n if (typeof patch.coverAssetId === 'string') {\n await this.requireOwnedImage(orgId, actor.id, patch.coverAssetId);\n }\n if (!this.patchChanges(post, patch)) {\n return this.viewOf(orgId, actor, post);\n }\n const retext = patch.title !== undefined || patch.body !== undefined;\n const candidates = retext\n ? await this.mentionable(\n orgId,\n post.spaceId,\n patch.title ?? post.title,\n patch.body ?? post.body,\n )\n : [];\n const updated = await this.uow.run(async ({ posts, mentions, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, patch);\n const added = retext\n ? await mentions.sync(orgId, 'post', postId, candidates, new Date())\n : [];\n await outbox.append([\n this.updated(\n orgId,\n next,\n this.meta(actor, next.status === 'pending' ? post.status : undefined),\n next.status === 'published' ? keepAllowed(candidates, added) : [],\n ),\n ]);\n return next;\n });\n this.logger.info('post updated', {\n orgId,\n postId,\n fields: Object.keys(patch),\n actorId: actor.id,\n });\n return this.viewOf(orgId, actor, updated);\n }\n\n async submit(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n const { post, space } = await this.loadVisible(orgId, actor, postId);\n this.assertAuthor(actor, post);\n if (post.status !== 'draft' && post.status !== 'scheduled') {\n throw new ConflictError('This post cannot be submitted');\n }\n this.assertTopic(space, post.topicId);\n const saved = await this.commitSubmission(orgId, post, space, actor.role, actor, new Date());\n this.logger.info('post submitted', { orgId, postId, status: saved.status, actorId: actor.id });\n if (post.status === 'scheduled') {\n await this.disarm(orgId, postId);\n }\n return this.viewOf(orgId, actor, saved);\n }\n\n async schedule(\n orgId: string,\n postId: string,\n actor: Actor,\n scheduledAt: Date,\n ): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n this.assertAuthor(actor, post);\n if (post.status === 'published' || post.status === 'removed' || post.status === 'pending') {\n throw new ConflictError('This post cannot be scheduled');\n }\n if (scheduledAt <= new Date()) {\n throw new InvalidInputError('A scheduled time must be in the future');\n }\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await posts.updateIf(\n orgId,\n postId,\n { status: post.status },\n { status: 'scheduled', scheduledAt, publishedAt: null, removedBy: null },\n );\n if (!next) {\n throw new ConflictError('This post cannot be scheduled');\n }\n await outbox.append([this.updated(orgId, next, this.meta(actor, post.status))]);\n return next;\n });\n this.logger.info('post scheduled', {\n orgId,\n postId,\n scheduledAt: scheduledAt.toISOString(),\n actorId: actor.id,\n });\n await this.arm(orgId, actor, updated, post.status === 'scheduled' ? post.scheduledAt : null);\n return this.viewOf(orgId, actor, updated);\n }\n\n async unschedule(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n this.assertAuthor(actor, post);\n if (post.status !== 'scheduled') {\n throw new ConflictError('This post is not scheduled');\n }\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await posts.updateIf(orgId, postId, { status: 'scheduled' }, DRAFT);\n if (!next) {\n throw new ConflictError('This post is not scheduled');\n }\n await outbox.append([this.updated(orgId, next, this.meta(actor, post.status))]);\n return next;\n });\n this.logger.info('post unscheduled', { orgId, postId, actorId: actor.id });\n await this.disarm(orgId, postId);\n return this.viewOf(orgId, actor, updated);\n }\n\n async publishScheduled(orgId: string, postId: string, now: Date): Promise<void> {\n const post = await this.repo.find(orgId, postId);\n if (!post || post.status !== 'scheduled' || post.scheduledAt === null) {\n this.logger.debug('scheduled post no longer scheduled', { orgId, postId });\n return;\n }\n if (post.scheduledAt > now) {\n await this.scheduler.at(POST_PUBLISH_JOB, post.scheduledAt, postId, { orgId, postId });\n this.logger.info('scheduled post timer moved', {\n orgId,\n postId,\n scheduledAt: post.scheduledAt.toISOString(),\n });\n return;\n }\n const space = await this.content.getSpace(orgId, post.spaceId);\n if (!space) {\n this.logger.error('scheduled post has no space', { orgId, postId, spaceId: post.spaceId });\n return;\n }\n const role = await this.authors.roleOf(orgId, post.orgUserId);\n const refused = !(await this.submittable(orgId, post, space, role));\n const patch = refused ? DRAFT : this.submissionOf(space.settings.requireReview, role, now);\n const saved = await this.uow.run(async ({ posts, mentions, outbox }) => {\n const next = await posts.updateIf(orgId, postId, { status: 'scheduled', dueBy: now }, patch);\n if (!next) {\n return null;\n }\n await outbox.append([await this.landed(mentions, orgId, next, this.meta(null, post.status))]);\n return next;\n });\n if (!saved) {\n this.logger.debug('scheduled post already handled', { orgId, postId });\n return;\n }\n this.logger.info('scheduled post submitted', { orgId, postId, status: saved.status, refused });\n }\n\n private async arm(\n orgId: string,\n actor: Actor,\n post: Post,\n previousScheduledAt: Date | null,\n ): Promise<void> {\n if (post.scheduledAt === null) {\n return;\n }\n try {\n await this.scheduler.at(POST_PUBLISH_JOB, post.scheduledAt, post.id, {\n orgId,\n postId: post.id,\n });\n return;\n } catch (err) {\n this.logger.error('post timer not set', { orgId, postId: post.id, err });\n }\n await this.uow.run(async ({ posts, outbox }) => {\n const reverted = await posts.updateIf(\n orgId,\n post.id,\n { status: 'scheduled', scheduledAt: post.scheduledAt! },\n previousScheduledAt ? { scheduledAt: previousScheduledAt } : DRAFT,\n );\n if (!reverted) {\n return;\n }\n await outbox.append([\n this.updated(\n orgId,\n reverted,\n this.meta(actor, reverted.status === 'draft' ? post.status : undefined),\n ),\n ]);\n });\n throw new UnavailableError('The post could not be scheduled; try again', { postId: post.id });\n }\n\n private async disarm(orgId: string, postId: string): Promise<void> {\n try {\n await this.scheduler.cancel(POST_PUBLISH_JOB, postId);\n } catch (err) {\n this.logger.warn('post timer not cancelled', { orgId, postId, err });\n }\n }\n\n async approve(\n orgId: string,\n postId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId, communityId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot review this post');\n }\n if (post.status !== 'pending') {\n throw new ConflictError('This post is not awaiting review');\n }\n const now = new Date();\n const updated = await this.uow.run(async ({ posts, mentions, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, {\n status: 'published',\n publishedAt: now,\n scheduledAt: null,\n removedBy: null,\n });\n await outbox.append([\n await this.landed(mentions, orgId, next, this.meta(actor, post.status)),\n ]);\n return next;\n });\n this.logger.info('post approved', { orgId, postId, actorId: actor.id });\n return this.viewOf(orgId, actor, updated);\n }\n\n async remove(\n orgId: string,\n postId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId, communityId);\n if (post.orgUserId !== actor.id && !this.canModerate(actor)) {\n throw new ForbiddenError('You cannot remove this post');\n }\n if (post.status === 'removed') {\n throw new ConflictError('This post was removed');\n }\n if (post.status !== 'published' && post.status !== 'pending') {\n throw new ConflictError('This post cannot be removed');\n }\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, {\n status: 'removed',\n removedBy: actor.id,\n });\n if (post.orgUserId !== actor.id) {\n await posts.resolveFlags(orgId, postId, {\n resolvedAt: new Date(),\n resolvedBy: actor.id,\n resolution: 'removed',\n });\n }\n await outbox.append([\n discussionEvents.postRemoved.make({\n orgId,\n data: next,\n metadata: this.meta(actor, post.status),\n }),\n ]);\n return next;\n });\n this.logger.info('post removed', { orgId, postId, actorId: actor.id });\n return this.viewOf(orgId, actor, updated);\n }\n\n async restore(\n orgId: string,\n postId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<PostView> {\n const { post, space } = await this.loadVisible(orgId, actor, postId, communityId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot moderate this post');\n }\n if (post.status !== 'removed') {\n throw new ConflictError('This post was not removed');\n }\n const now = new Date();\n const role = await this.authors.roleOf(orgId, post.orgUserId);\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(\n posts,\n orgId,\n postId,\n this.submissionOf(space.settings.requireReview, role, now),\n );\n const metadata = this.meta(actor, post.status);\n await outbox.append([\n next.status === 'published'\n ? this.published(orgId, next, metadata, [])\n : this.updated(orgId, next, metadata),\n ]);\n return next;\n });\n this.logger.info('post restored', {\n orgId,\n postId,\n status: updated.status,\n actorId: actor.id,\n });\n return this.viewOf(orgId, actor, updated);\n }\n\n pin(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { pinned: true },\n (post) => post.pinned,\n 'post pinned',\n );\n }\n\n unpin(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { pinned: false },\n (post) => !post.pinned,\n 'post unpinned',\n );\n }\n\n closeComments(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { commentsClosed: true },\n (post) => post.commentsClosed === true,\n 'post comments closed',\n );\n }\n\n openComments(orgId: string, postId: string, actor: Actor): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { commentsClosed: false },\n (post) => post.commentsClosed === false,\n 'post comments opened',\n );\n }\n\n setReactions(orgId: string, postId: string, actor: Actor, enabled: boolean): Promise<PostView> {\n return this.moderate(\n orgId,\n postId,\n actor,\n { reactionsEnabled: enabled },\n (post) => post.reactionsEnabled === enabled,\n 'post reactions changed',\n );\n }\n\n async setReaction(\n orgId: string,\n postId: string,\n actor: Actor,\n emoji: ReactionEmoji | null,\n ): Promise<PostReactions> {\n const { post, space } = await this.loadVisible(orgId, actor, postId);\n if (!(post.reactionsEnabled ?? space.settings.reactions)) {\n throw new ForbiddenError('Reactions are off on this post');\n }\n await this.uow.run(async ({ posts, outbox }) => {\n const first = await posts.setReaction(orgId, postId, actor.id, emoji);\n if (!first || emoji === null) {\n return;\n }\n await outbox.append([\n discussionEvents.postReactionAdded.make({\n orgId,\n data: {\n postId,\n spaceId: post.spaceId,\n communityId: post.communityId,\n orgUserId: actor.id,\n reaction: emoji,\n },\n metadata: this.meta(actor),\n }),\n ]);\n });\n this.logger.debug('post reaction set', { orgId, postId, orgUserId: actor.id });\n const counts = await this.repo.reactionsOf(orgId, [postId], actor.id);\n return counts[postId] ?? { reactions: {} };\n }\n\n async bookmark(orgId: string, postId: string, actor: Actor): Promise<void> {\n await this.loadVisible(orgId, actor, postId);\n await this.repo.bookmark(orgId, postId, actor.id);\n this.logger.debug('post bookmarked', { orgId, postId, orgUserId: actor.id });\n }\n\n async unbookmark(orgId: string, postId: string, actor: Actor): Promise<void> {\n await this.loadVisible(orgId, actor, postId);\n await this.repo.unbookmark(orgId, postId, actor.id);\n this.logger.debug('post unbookmarked', { orgId, postId, orgUserId: actor.id });\n }\n\n async flag(orgId: string, postId: string, actor: Actor, reason: string): Promise<PostFlag> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n const now = new Date();\n const flag: PostFlag = {\n orgId,\n id: genId('postFlag'),\n postId,\n orgUserId: actor.id,\n reason,\n resolvedAt: null,\n resolution: null,\n resolvedBy: null,\n createdAt: now,\n updatedAt: now,\n };\n const saved = await this.uow.run(async ({ posts, outbox }) => {\n const created = await posts.insertFlag(orgId, flag);\n if (!created) {\n const existing = await posts.findFlag(orgId, postId, actor.id);\n if (!existing) {\n throw new NotFoundError('Flag', postId);\n }\n return existing;\n }\n await outbox.append([\n discussionEvents.postReported.make({\n orgId,\n data: { ...created, communityId: post.communityId },\n metadata: this.meta(actor),\n }),\n ]);\n return created;\n });\n this.logger.info('post flagged', { orgId, postId, flagId: saved.id, actorId: actor.id });\n return saved;\n }\n\n async resolveFlags(\n orgId: string,\n postId: string,\n actor: Actor,\n resolution: Extract<PostFlagResolution, 'dismissed'>,\n communityId?: string,\n ): Promise<void> {\n await this.loadVisible(orgId, actor, postId, communityId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot work this queue');\n }\n await this.repo.resolveFlags(orgId, postId, {\n resolvedAt: new Date(),\n resolvedBy: actor.id,\n resolution,\n });\n this.logger.info('post flags resolved', { orgId, postId, resolution, actorId: actor.id });\n }\n\n async spaceFeed(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n query: FeedQuery,\n ): Promise<Page<PostView>> {\n const space = await this.requireOpen(orgId, actor, spaceId, communityId);\n if (query.topicId && !space.topics.some((topic) => topic.id === query.topicId)) {\n throw new NotFoundError('Topic', query.topicId);\n }\n const page = await this.feedPage(orgId, actor, [spaceId], query);\n this.logger.debug('space feed read', { orgId, spaceId, rows: page.rows.length });\n return page;\n }\n\n async memberFeed(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n ): Promise<Page<PostView>> {\n const spaceIds = await this.openSpaceIdsIn(orgId, actor, communityId);\n const page = await this.feedPage(orgId, actor, spaceIds, query);\n this.logger.debug('member feed read', {\n orgId,\n communityId,\n spaces: spaceIds.length,\n rows: page.rows.length,\n });\n return page;\n }\n\n private async openSpaceIdsIn(\n orgId: string,\n actor: Actor,\n communityId: string,\n ): Promise<string[]> {\n if (!(await this.access.isCommunityMember(orgId, actor, communityId))) {\n throw new NotFoundError('Community', communityId);\n }\n return this.access.openSpaceIds(orgId, actor, communityId);\n }\n\n async listOwn(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<PostView>> {\n const spaceIds = await this.openSpaceIdsIn(orgId, actor, communityId);\n const page = await this.repo.listByAuthor(orgId, actor.id, spaceIds, query);\n this.logger.debug('own posts read', { orgId, communityId, rows: page.rows.length });\n return { ...page, rows: await this.viewsOf(orgId, actor, page.rows) };\n }\n\n async listBookmarked(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<PostView>> {\n const spaceIds = await this.openSpaceIdsIn(orgId, actor, communityId);\n const page = await this.repo.listBookmarked(orgId, actor.id, spaceIds, {\n ...query,\n visibleStatuses: this.visibleStatuses(actor),\n });\n this.logger.debug('bookmarked posts read', { orgId, communityId, rows: page.rows.length });\n return { ...page, rows: await this.viewsOf(orgId, actor, page.rows) };\n }\n\n async coverFile(orgId: string, actor: Actor, postId: string): Promise<string | null> {\n const post = await this.repo.find(orgId, postId);\n if (!post?.coverAssetId || !this.canSee(actor, post)) {\n return null;\n }\n const space = await this.content.getSpace(orgId, post.spaceId);\n if (!space || !(await this.access.canOpen(orgId, actor, space))) {\n return null;\n }\n return this.images.signFile(orgId, post.coverAssetId);\n }\n\n async getPost(orgId: string, actor: Actor, postId: string): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n this.logger.debug('post read', { orgId, postId });\n return this.viewOf(orgId, actor, post);\n }\n\n async moderationQueue(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceModerationQueue> {\n await this.requireOpen(orgId, actor, spaceId, communityId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot work this queue');\n }\n const [pending, flags, openCommentFlags] = await Promise.all([\n this.repo.listPending(orgId, spaceId, MODERATION_QUEUE_LIMIT),\n this.repo.listOpenFlags(orgId, spaceId, MODERATION_QUEUE_LIMIT),\n this.comments.listOpenFlagsForSpace(orgId, spaceId, MODERATION_QUEUE_LIMIT),\n ]);\n this.logger.debug('space queue read', {\n orgId,\n spaceId,\n pending: pending.length,\n flags: flags.length,\n commentFlags: openCommentFlags.length,\n });\n return {\n pending: await this.viewsOf(orgId, actor, pending),\n flags,\n commentFlags: openCommentFlags,\n };\n }\n\n visible(\n orgId: string,\n actor: Actor,\n postId: string,\n ): Promise<{ post: Post; space: SpaceDetail }> {\n return this.loadVisible(orgId, actor, postId);\n }\n\n find(orgId: string, postId: string): Promise<Post | null> {\n return this.repo.find(orgId, postId);\n }\n\n private async submittable(\n orgId: string,\n post: Post,\n space: SpaceDetail,\n role: Role | null,\n ): Promise<boolean> {\n if (role === null || !this.topicFits(space, post.topicId)) {\n return false;\n }\n return this.access.canOpen(orgId, { id: post.orgUserId, role }, space);\n }\n\n private async feedPage(\n orgId: string,\n actor: Actor,\n spaceIds: string[],\n query: FeedQuery,\n ): Promise<Page<PostView>> {\n const page = await this.repo.listPublished(orgId, spaceIds, query);\n return { ...page, rows: await this.viewsOf(orgId, actor, page.rows) };\n }\n\n private async viewOf(orgId: string, actor: Actor, post: Post): Promise<PostView> {\n const [view] = await this.viewsOf(orgId, actor, [post]);\n return view!;\n }\n\n private async viewsOf(orgId: string, actor: Actor, posts: Post[]): Promise<PostView[]> {\n if (posts.length === 0) {\n return [];\n }\n const postIds = posts.map((post) => post.id);\n const now = new Date();\n const [records, reactions, commentCounts, subscribed, bookmarked, mentioned] =\n await Promise.all([\n this.profiles.authorsOf(orgId, [...new Set(posts.map((post) => post.orgUserId))]),\n this.repo.reactionsOf(orgId, postIds, actor.id),\n this.comments.publishedCommentCounts(orgId, postIds),\n this.subscriptions.forViewer(orgId, actor.id, 'post', postIds),\n this.repo.bookmarkedAmong(orgId, postIds, actor.id),\n mentionViews(this.mentions, this.profiles, orgId, 'post', postIds),\n ]);\n const subscriptionOf = new Map(subscribed.map((row) => [row.itemId, row]));\n const saved = new Set(bookmarked);\n return posts.map((post) => {\n const record = records[post.orgUserId];\n if (!record) {\n throw new NotFoundError('Member', post.orgUserId);\n }\n const counts = reactions[post.id];\n return {\n ...post,\n coverUrl: post.coverAssetId ? this.fileUrls.postCover(post.id) : null,\n author: authorView(orgId, record, this.fileUrls),\n reactions: counts?.reactions ?? {},\n viewerReaction: counts?.viewerReaction ?? null,\n commentCount: commentCounts[post.id] ?? 0,\n subscription: activeSubscription(subscriptionOf.get(post.id), now),\n bookmarked: saved.has(post.id),\n mentions: mentioned[post.id] ?? [],\n };\n });\n }\n\n private async commitSubmission(\n orgId: string,\n post: Post,\n space: SpaceDetail,\n role: Role,\n actor: Actor,\n now: Date,\n ): Promise<Post> {\n return this.uow.run(async ({ posts, mentions, outbox }) => {\n const next = await posts.updateIf(\n orgId,\n post.id,\n { status: post.status },\n this.submissionOf(space.settings.requireReview, role, now),\n );\n if (!next) {\n throw new ConflictError('This post cannot be submitted');\n }\n await outbox.append([\n await this.landed(mentions, orgId, next, this.meta(actor, post.status)),\n ]);\n return next;\n });\n }\n\n private async moderate(\n orgId: string,\n postId: string,\n actor: Actor,\n patch: PostPatch,\n unchanged: (post: Post) => boolean,\n log: string,\n ): Promise<PostView> {\n const { post } = await this.loadVisible(orgId, actor, postId);\n if (!this.canModerate(actor)) {\n throw new ForbiddenError('You cannot moderate this post');\n }\n if (post.status === 'removed') {\n throw new ConflictError('This post was removed');\n }\n if (unchanged(post)) {\n return this.viewOf(orgId, actor, post);\n }\n const updated = await this.uow.run(async ({ posts, outbox }) => {\n const next = await this.mustUpdate(posts, orgId, postId, patch);\n await outbox.append([\n this.updated(\n orgId,\n next,\n this.meta(actor, next.status === 'pending' ? post.status : undefined),\n ),\n ]);\n return next;\n });\n this.logger.info(log, { orgId, postId, actorId: actor.id });\n return this.viewOf(orgId, actor, updated);\n }\n\n private async mustUpdate(\n posts: PostRepository,\n orgId: string,\n postId: string,\n patch: PostPatch,\n ): Promise<Post> {\n const next = await posts.update(orgId, postId, patch);\n if (!next) {\n throw new NotFoundError('Post', postId);\n }\n return next;\n }\n\n private initial(input: CreatePostInput, space: SpaceDetail, actor: Actor, now: Date) {\n if (input.mode === 'draft') {\n return { status: 'draft' as const, scheduledAt: null, publishedAt: null, removedBy: null };\n }\n if (input.mode === 'schedule') {\n if (!input.scheduledAt) {\n throw new InvalidInputError('A scheduled post needs a time');\n }\n if (input.scheduledAt <= now) {\n throw new InvalidInputError('A scheduled time must be in the future');\n }\n return {\n status: 'scheduled' as const,\n scheduledAt: input.scheduledAt,\n publishedAt: null,\n removedBy: null,\n };\n }\n return this.submissionOf(space.settings.requireReview, actor.role, now);\n }\n\n private submissionOf(requireReview: boolean, role: Role | null, now: Date) {\n if (requireReview && !(role && isStaffRole(role))) {\n return { status: 'pending' as const, scheduledAt: null, publishedAt: null, removedBy: null };\n }\n return { status: 'published' as const, scheduledAt: null, publishedAt: now, removedBy: null };\n }\n\n private async requireOwnedImage(\n orgId: string,\n orgUserId: string,\n assetId: string | null,\n ): Promise<void> {\n if (assetId === null) {\n return;\n }\n if (!(await this.images.ownsImage(orgId, assetId, orgUserId))) {\n this.logger.debug('cover refused', { orgId, orgUserId, assetId });\n throw new NotFoundError('Asset', assetId);\n }\n }\n\n private async openSpace(\n orgId: string,\n actor: Actor,\n spaceId: string,\n communityId?: string,\n ): Promise<SpaceDetail | null> {\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space || (communityId !== undefined && space.communityId !== communityId)) {\n return null;\n }\n return (await this.access.canOpen(orgId, actor, space)) ? space : null;\n }\n\n private async requireOpen(\n orgId: string,\n actor: Actor,\n spaceId: string,\n communityId?: string,\n ): Promise<SpaceDetail> {\n const space = await this.openSpace(orgId, actor, spaceId, communityId);\n if (!space) {\n throw new NotFoundError('Space', spaceId);\n }\n return space;\n }\n\n private async loadVisible(orgId: string, actor: Actor, postId: string, communityId?: string) {\n const post = await this.repo.find(orgId, postId);\n if (!post || (communityId !== undefined && post.communityId !== communityId)) {\n throw new NotFoundError('Post', postId);\n }\n const space = await this.openSpace(orgId, actor, post.spaceId);\n if (!space || !this.canSee(actor, post)) {\n throw new NotFoundError('Post', postId);\n }\n return { post, space };\n }\n\n private canSee(actor: Actor, post: Post): boolean {\n return post.orgUserId === actor.id || this.visibleStatuses(actor).includes(post.status);\n }\n\n private visibleStatuses(actor: Actor): PostStatus[] {\n return this.canModerate(actor) ? MODERATOR_VISIBLE : PUBLISHED_ONLY;\n }\n\n private canModerate(actor: Actor): boolean {\n return canModerate(actor.role);\n }\n\n private assertAuthor(actor: Actor, post: Post): void {\n if (post.orgUserId !== actor.id) {\n throw new ForbiddenError('You can change only your own post');\n }\n }\n\n private topicFits(space: SpaceDetail, topicId: string | null): boolean {\n if (topicId === null) {\n return space.topics.length === 0;\n }\n return space.topics.some((topic) => topic.id === topicId);\n }\n\n private assertTopic(space: SpaceDetail, topicId: string | null): void {\n if (this.topicFits(space, topicId)) {\n return;\n }\n if (topicId === null) {\n throw new InvalidInputError('This space requires a topic');\n }\n throw new NotFoundError('Topic', topicId);\n }\n\n private patchChanges(post: Post, patch: UpdatePostInput): boolean {\n return (Object.keys(patch) as (keyof UpdatePostInput)[]).some((key) => {\n const value = patch[key];\n if (value === undefined) {\n return false;\n }\n if (key === 'extra') {\n return JSON.stringify(post.extra) !== JSON.stringify(value);\n }\n return post[key] !== value;\n });\n }\n\n private async mentionable(\n orgId: string,\n spaceId: string,\n title: string,\n body: string,\n ): Promise<string[]> {\n return this.openersOf(orgId, spaceId, mentionTokens(title, body));\n }\n\n private async openersOf(orgId: string, spaceId: string, orgUserIds: string[]): Promise<string[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n return keepAllowed(orgUserIds, await this.access.spaceOpenersAmong(orgId, spaceId, orgUserIds));\n }\n\n private async landed(\n mentions: MentionsRepository,\n orgId: string,\n post: Post,\n metadata: EventMeta,\n ): Promise<NewDiscussionEvent> {\n if (post.status !== 'published') {\n return this.updated(orgId, post, metadata);\n }\n const mentioned = await this.openersOf(\n orgId,\n post.spaceId,\n await mentions.ofItem(orgId, 'post', post.id),\n );\n return this.published(orgId, post, metadata, mentioned);\n }\n\n private published(\n orgId: string,\n post: Post,\n metadata: EventMeta,\n mentionedOrgUserIds: string[],\n ): NewDiscussionEvent {\n return discussionEvents.postPublished.make({\n orgId,\n data: { ...post, mentionedOrgUserIds },\n metadata,\n });\n }\n\n private updated(\n orgId: string,\n post: Post,\n metadata: EventMeta,\n mentionedOrgUserIds: string[] = [],\n ): NewDiscussionEvent {\n return discussionEvents.postUpdated.make({\n orgId,\n data: { ...post, mentionedOrgUserIds },\n metadata,\n });\n }\n\n private meta(actor: Actor | null, previousStatus?: PostStatus | null): EventMeta {\n return {\n ...(actor ? { actorOrgUserId: actor.id } : {}),\n ...(previousStatus !== undefined ? { previousStatus } : {}),\n };\n }\n}\n"],"mappings":"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"}
@@ -6,10 +6,12 @@ import "../shared/ports.js";
6
6
  import { FileUrls } from "../assets/file-urls.js";
7
7
  import "../assets/index.js";
8
8
  import { Actor } from "../shared/actor.js";
9
+ import { SettingsService } from "../shared/settings.js";
10
+ import { CommunityAccessReader } from "../entitlements/ports.js";
11
+ import "../entitlements/index.js";
9
12
  import "./model.js";
10
13
  import "./types.js";
11
- import { ActivityComments, ActivityGetter, CommentReactions, ContentAccessReader, DiscussionRepository, DiscussionService, DiscussionUnitOfWork, PageQuery, PostCommentInput, PostService } from "./ports.js";
12
- import { SettingsService } from "../shared/settings.js";
14
+ import { ActivityComments, ActivityGetter, CommentReactions, ContentAccessReader, DiscussionRepository, DiscussionService, DiscussionUnitOfWork, MentionsRepository, PageQuery, PostCommentInput, PostService } from "./ports.js";
13
15
  //#region src/discussion/service.d.ts
14
16
  /** A course with no stored settings. Discussion is opt-in, so the common case
15
17
  * persists no row at all and every existing course stays silent. */
@@ -23,12 +25,11 @@ type DiscussionServiceParams = {
23
25
  repo: DiscussionRepository;
24
26
  access: ContentAccessReader;
25
27
  content: ActivityGetter;
26
- posts: Pick<PostService, 'visible'>;
28
+ posts: Pick<PostService, 'visible' | 'find'>;
29
+ spaces: Pick<CommunityAccessReader, 'spaceOpenersAmong'>;
30
+ mentions: MentionsRepository;
27
31
  uow: DiscussionUnitOfWork;
28
32
  settings: SettingsService;
29
- communities: {
30
- listIds(orgId: string): Promise<string[]>;
31
- };
32
33
  fileUrls: FileUrls;
33
34
  logger?: Logger;
34
35
  };
@@ -37,9 +38,10 @@ declare class DiscussionServiceImpl implements DiscussionService {
37
38
  private readonly access;
38
39
  private readonly content;
39
40
  private readonly posts;
41
+ private readonly spaces;
42
+ private readonly mentions;
40
43
  private readonly uow;
41
44
  private readonly settings;
42
- private readonly communities;
43
45
  private readonly fileUrls;
44
46
  private readonly logger;
45
47
  constructor(params: DiscussionServiceParams);
@@ -51,6 +53,14 @@ declare class DiscussionServiceImpl implements DiscussionService {
51
53
  private activityIdOf;
52
54
  private hasAccess;
53
55
  private requireActivityOpen;
56
+ private courseOpeners;
57
+ private spaceOpeners;
58
+ private openersOf;
59
+ commenterIds(orgId: string, target: {
60
+ postId: string;
61
+ } | {
62
+ activityId: string;
63
+ }): Promise<string[]>;
54
64
  postComment(orgId: string, input: PostCommentInput): Promise<CommentView>;
55
65
  private postOnActivity;
56
66
  private postOnPost;
@@ -82,7 +92,6 @@ declare class DiscussionServiceImpl implements DiscussionService {
82
92
  reportComment(orgId: string, commentId: string, actor: Actor, reason: string): Promise<CommentReport>;
83
93
  listComments(orgId: string, query: ListCommentsQuery): Promise<Page<CommentListItem>>;
84
94
  getComment(orgId: string, commentId: string): Promise<Comment | null>;
85
- sendDigest(orgId: string, now: Date): Promise<void>;
86
95
  }
87
96
  //#endregion
88
97
  export { DEFAULT_SETTINGS, DiscussionServiceImpl, DiscussionServiceParams };
@@ -5,6 +5,7 @@ import { canModerate, isStaffRole } from "../organizations/roles.js";
5
5
  import "../shared/settings.js";
6
6
  import { discussionEvents } from "./events.js";
7
7
  import { authorView } from "./authors.js";
8
+ import { keepAllowed, mentionTokens, mentionViews } from "./mentions.js";
8
9
  //#region src/discussion/service.ts
9
10
  /** A course with no stored settings. Discussion is opt-in, so the common case
10
11
  * persists no row at all and every existing course stays silent. */
@@ -20,9 +21,10 @@ var DiscussionServiceImpl = class {
20
21
  access;
21
22
  content;
22
23
  posts;
24
+ spaces;
25
+ mentions;
23
26
  uow;
24
27
  settings;
25
- communities;
26
28
  fileUrls;
27
29
  logger;
28
30
  constructor(params) {
@@ -30,9 +32,10 @@ var DiscussionServiceImpl = class {
30
32
  this.access = params.access;
31
33
  this.content = params.content;
32
34
  this.posts = params.posts;
35
+ this.spaces = params.spaces;
36
+ this.mentions = params.mentions;
33
37
  this.uow = params.uow;
34
38
  this.settings = params.settings;
35
- this.communities = params.communities;
36
39
  this.fileUrls = params.fileUrls;
37
40
  this.logger = params.logger ?? noopLogger;
38
41
  }
@@ -55,7 +58,7 @@ var DiscussionServiceImpl = class {
55
58
  isStaff(actor) {
56
59
  return isStaffRole(actor.role);
57
60
  }
58
- toView(orgId, comment, authors, urls, reactions) {
61
+ toView(orgId, comment, authors, urls, reactions, mentions) {
59
62
  const author = authors[comment.orgUserId];
60
63
  if (!author) throw new NotFoundError("Member", comment.orgUserId);
61
64
  if (comment.removedBy && !authors[comment.removedBy]) throw new NotFoundError("Member", comment.removedBy);
@@ -72,6 +75,7 @@ var DiscussionServiceImpl = class {
72
75
  removedBy: remover ? authorView(orgId, remover, urls) : null,
73
76
  reactions: reactions.reactions,
74
77
  viewerReaction: reactions.viewerReaction,
78
+ mentions: comment.status === "removed" ? [] : mentions,
75
79
  createdAt: comment.createdAt,
76
80
  updatedAt: comment.updatedAt
77
81
  };
@@ -85,9 +89,9 @@ var DiscussionServiceImpl = class {
85
89
  return [...ids];
86
90
  }
87
91
  async renderOne(orgId, comment, actor) {
88
- const authors = await this.repo.authorsOf(orgId, this.peopleIn([comment]));
92
+ const [authors, mentioned] = await Promise.all([this.repo.authorsOf(orgId, this.peopleIn([comment])), mentionViews(this.mentions, this.repo, orgId, "comment", [comment.id])]);
89
93
  const stored = comment.status === "removed" ? EMPTY_REACTIONS : (await this.repo.reactionsOf(orgId, [comment.id], actor.id))[comment.id] ?? EMPTY_REACTIONS;
90
- return this.toView(orgId, comment, authors, this.fileUrls, stored);
94
+ return this.toView(orgId, comment, authors, this.fileUrls, stored, mentioned[comment.id] ?? []);
91
95
  }
92
96
  activityIdOf(comment) {
93
97
  if (comment.activityId === null) throw new NotFoundError("Activity", comment.id);
@@ -102,6 +106,33 @@ var DiscussionServiceImpl = class {
102
106
  const activity = await this.content.getActivity(orgId, activityId);
103
107
  if (!activity) throw new NotFoundError("Activity", activityId);
104
108
  if (!this.isStaff(actor) && !await this.access.hasAccess(orgId, actor.id, activity.courseId)) throw new ForbiddenError("You cannot open this activity");
109
+ return activity;
110
+ }
111
+ async courseOpeners(orgId, courseId, orgUserIds) {
112
+ if (orgUserIds.length === 0) return [];
113
+ const people = await this.repo.authorsOf(orgId, orgUserIds);
114
+ const opens = await Promise.all(orgUserIds.map(async (orgUserId) => {
115
+ const person = people[orgUserId];
116
+ if (!person) return false;
117
+ return isStaffRole(person.role) || this.access.hasAccess(orgId, orgUserId, courseId);
118
+ }));
119
+ return orgUserIds.filter((_, index) => opens[index]);
120
+ }
121
+ async spaceOpeners(orgId, spaceId, orgUserIds) {
122
+ if (orgUserIds.length === 0) return [];
123
+ return keepAllowed(orgUserIds, await this.spaces.spaceOpenersAmong(orgId, spaceId, orgUserIds));
124
+ }
125
+ async openersOf(orgId, comment, orgUserIds) {
126
+ if (orgUserIds.length === 0) return [];
127
+ if (comment.postId) {
128
+ const post = await this.posts.find(orgId, comment.postId);
129
+ return post ? this.spaceOpeners(orgId, post.spaceId, orgUserIds) : [];
130
+ }
131
+ const activity = await this.content.getActivity(orgId, this.activityIdOf(comment));
132
+ return activity ? this.courseOpeners(orgId, activity.courseId, orgUserIds) : [];
133
+ }
134
+ commenterIds(orgId, target) {
135
+ return this.repo.commenterIds(orgId, target);
105
136
  }
106
137
  async postComment(orgId, input) {
107
138
  if (input.postId) return this.postOnPost(orgId, input.postId, input);
@@ -110,7 +141,7 @@ var DiscussionServiceImpl = class {
110
141
  return this.postOnActivity(orgId, activityId, input);
111
142
  }
112
143
  async postOnActivity(orgId, activityId, { actor, parentId, body }) {
113
- await this.requireActivityOpen(orgId, activityId, actor);
144
+ const activity = await this.requireActivityOpen(orgId, activityId, actor);
114
145
  const config = await this.resolveConfig(orgId, activityId);
115
146
  if (!config.enabled) throw new ForbiddenError("Discussion is not open on this activity");
116
147
  if (parentId !== null) {
@@ -135,11 +166,17 @@ var DiscussionServiceImpl = class {
135
166
  createdAt: /* @__PURE__ */ new Date(),
136
167
  updatedAt: /* @__PURE__ */ new Date()
137
168
  };
169
+ const mentioned = await this.courseOpeners(orgId, activity.courseId, mentionTokens(body));
138
170
  const saved = await this.uow.run(async (scope) => {
139
171
  const row = await scope.discussion.insertComment(orgId, comment);
172
+ await scope.mentions.sync(orgId, "comment", row.id, mentioned, comment.createdAt);
140
173
  await scope.outbox.append([discussionEvents.commentCreated.make({
141
174
  orgId,
142
- data: row
175
+ data: {
176
+ ...row,
177
+ mentionedOrgUserIds: status === "published" ? mentioned : []
178
+ },
179
+ metadata: { actorOrgUserId: actor.id }
143
180
  })]);
144
181
  this.logger.info("comment created", {
145
182
  orgId,
@@ -176,11 +213,16 @@ var DiscussionServiceImpl = class {
176
213
  createdAt: now,
177
214
  updatedAt: now
178
215
  };
216
+ const mentioned = await this.spaceOpeners(orgId, post.spaceId, mentionTokens(body));
179
217
  const saved = await this.uow.run(async (scope) => {
180
218
  const row = await scope.discussion.insertComment(orgId, comment);
219
+ await scope.mentions.sync(orgId, "comment", row.id, mentioned, now);
181
220
  await scope.outbox.append([discussionEvents.commentCreated.make({
182
221
  orgId,
183
- data: row,
222
+ data: {
223
+ ...row,
224
+ mentionedOrgUserIds: mentioned
225
+ },
184
226
  metadata: { actorOrgUserId: actor.id }
185
227
  })]);
186
228
  this.logger.info("comment created", {
@@ -223,9 +265,13 @@ var DiscussionServiceImpl = class {
223
265
  const roots = readable.filter((comment) => comment.parentId === null && (comment.status !== "removed" || heldOpen.has(comment.id)));
224
266
  const rootIds = new Set(roots.map((root) => root.id));
225
267
  const served = [...roots, ...replies.filter((reply) => rootIds.has(reply.parentId))].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
226
- const [authors, reactions] = await Promise.all([this.repo.authorsOf(orgId, this.peopleIn(served)), reactionsOn ? this.repo.reactionsOf(orgId, served.map((comment) => comment.id), actor.id) : Promise.resolve({})]);
268
+ const [authors, reactions, mentioned] = await Promise.all([
269
+ this.repo.authorsOf(orgId, this.peopleIn(served)),
270
+ reactionsOn ? this.repo.reactionsOf(orgId, served.map((comment) => comment.id), actor.id) : Promise.resolve({}),
271
+ mentionViews(this.mentions, this.repo, orgId, "comment", served.map((comment) => comment.id))
272
+ ]);
227
273
  const urls = this.fileUrls;
228
- return served.map((comment) => this.toView(orgId, comment, authors, urls, reactions[comment.id] ?? EMPTY_REACTIONS));
274
+ return served.map((comment) => this.toView(orgId, comment, authors, urls, reactions[comment.id] ?? EMPTY_REACTIONS, mentioned[comment.id] ?? []));
229
275
  }
230
276
  async visiblePost(orgId, actor, postId) {
231
277
  return this.posts.visible(orgId, actor, postId);
@@ -265,8 +311,26 @@ var DiscussionServiceImpl = class {
265
311
  if (!config.enabled) throw new ForbiddenError(comment.postId ? "Comments are closed on this post" : "Discussion is not open on this activity");
266
312
  if (comment.orgUserId !== actor.id) throw new ForbiddenError("You can edit only your own comment");
267
313
  if (comment.status === "removed") throw new ForbiddenError("A removed comment cannot be edited");
268
- const updated = await this.repo.updateComment(orgId, commentId, { body });
269
- if (!updated) throw new NotFoundError("Comment", commentId);
314
+ const candidates = await this.openersOf(orgId, comment, mentionTokens(body));
315
+ const updated = await this.uow.run(async (scope) => {
316
+ const next = await scope.discussion.updateComment(orgId, commentId, { body });
317
+ if (!next) throw new NotFoundError("Comment", commentId);
318
+ const added = await scope.mentions.sync(orgId, "comment", commentId, candidates, /* @__PURE__ */ new Date());
319
+ await scope.outbox.append([discussionEvents.commentUpdated.make({
320
+ orgId,
321
+ data: {
322
+ ...next,
323
+ mentionedOrgUserIds: next.status === "published" ? keepAllowed(candidates, added) : []
324
+ },
325
+ metadata: { actorOrgUserId: actor.id }
326
+ })]);
327
+ return next;
328
+ });
329
+ this.logger.info("comment edited", {
330
+ orgId,
331
+ commentId,
332
+ actorId: actor.id
333
+ });
270
334
  return this.renderOne(orgId, updated, actor);
271
335
  }
272
336
  async remove(orgId, commentId, actor, communityId) {
@@ -304,7 +368,7 @@ var DiscussionServiceImpl = class {
304
368
  await scope.outbox.append([discussionEvents.commentRemoved.make({
305
369
  orgId,
306
370
  data: updated,
307
- metadata: updated.postId ? { actorOrgUserId: actor.id } : void 0
371
+ metadata: { actorOrgUserId: actor.id }
308
372
  })]);
309
373
  this.logger.info("comment removed", {
310
374
  orgId,
@@ -356,22 +420,39 @@ var DiscussionServiceImpl = class {
356
420
  threaded: true,
357
421
  requireReview: false,
358
422
  reactions: post.reactionsEnabled ?? space.settings.reactions
359
- }
423
+ },
424
+ spaceId: post.spaceId
360
425
  };
361
426
  }
362
427
  const activityId = this.activityIdOf(comment);
363
428
  if (!await this.hasAccess(orgId, activityId, actor)) throw new ForbiddenError("You cannot open this activity");
364
429
  return {
365
430
  comment,
366
- config: await this.resolveConfig(orgId, activityId)
431
+ config: await this.resolveConfig(orgId, activityId),
432
+ spaceId: null
367
433
  };
368
434
  }
369
435
  async setReaction(orgId, commentId, actor, emoji) {
370
- const { comment, config } = await this.loadWithConfig(orgId, commentId, actor);
436
+ const { comment, config, spaceId } = await this.loadWithConfig(orgId, commentId, actor);
371
437
  if (!config.enabled && !comment.postId) throw new ForbiddenError("Discussion is not open on this activity");
372
438
  if (!config.reactions && emoji !== null) throw new ForbiddenError(comment.postId ? "Reactions are off on this post" : "Reactions are off on this course");
373
439
  if (comment.status === "removed") throw new ForbiddenError("A removed comment cannot be reacted to");
374
- await this.repo.setReaction(orgId, commentId, actor.id, emoji);
440
+ await this.uow.run(async (scope) => {
441
+ if (!await scope.discussion.setReaction(orgId, commentId, actor.id, emoji) || emoji === null) return;
442
+ await scope.outbox.append([discussionEvents.commentReactionAdded.make({
443
+ orgId,
444
+ data: {
445
+ commentId,
446
+ postId: comment.postId,
447
+ activityId: comment.activityId,
448
+ spaceId,
449
+ communityId: comment.communityId,
450
+ orgUserId: actor.id,
451
+ reaction: emoji
452
+ },
453
+ metadata: { actorOrgUserId: actor.id }
454
+ })]);
455
+ });
375
456
  return (await this.repo.reactionsOf(orgId, [commentId], actor.id))[commentId] ?? EMPTY_REACTIONS;
376
457
  }
377
458
  async approve(orgId, commentId, actor) {
@@ -389,9 +470,14 @@ var DiscussionServiceImpl = class {
389
470
  return this.uow.run(async (scope) => {
390
471
  const updated = await scope.discussion.updateComment(orgId, commentId, { status: "published" });
391
472
  if (!updated) throw new NotFoundError("Comment", commentId);
473
+ const mentioned = await this.openersOf(orgId, updated, await scope.mentions.ofItem(orgId, "comment", commentId));
392
474
  await scope.outbox.append([discussionEvents.commentPublished.make({
393
475
  orgId,
394
- data: updated
476
+ data: {
477
+ ...updated,
478
+ mentionedOrgUserIds: mentioned
479
+ },
480
+ metadata: { actorOrgUserId: actor.id }
395
481
  })]);
396
482
  this.logger.info("comment published", {
397
483
  orgId,
@@ -474,7 +560,7 @@ var DiscussionServiceImpl = class {
474
560
  ...saved,
475
561
  communityId: comment.communityId
476
562
  },
477
- metadata: comment.postId ? { actorOrgUserId: actor.id } : void 0
563
+ metadata: { actorOrgUserId: actor.id }
478
564
  })]);
479
565
  this.logger.info("comment reported", {
480
566
  orgId,
@@ -530,28 +616,6 @@ var DiscussionServiceImpl = class {
530
616
  getComment(orgId, commentId) {
531
617
  return this.repo.findComment(orgId, commentId);
532
618
  }
533
- async sendDigest(orgId, now) {
534
- const communityIds = await this.communities.listIds(orgId);
535
- if (communityIds.length === 0) {
536
- this.logger.debug("no communities for digest", { orgId });
537
- return;
538
- }
539
- const period = now.toISOString().slice(0, 10);
540
- await this.uow.run(async ({ outbox }) => {
541
- await outbox.append(communityIds.map((communityId) => discussionEvents.digestDue.make({
542
- orgId,
543
- data: {
544
- communityId,
545
- period
546
- }
547
- })));
548
- });
549
- this.logger.info("digest due", {
550
- orgId,
551
- communities: communityIds.length,
552
- period
553
- });
554
- }
555
619
  };
556
620
  //#endregion
557
621
  export { DEFAULT_SETTINGS, DiscussionServiceImpl };