@headless-lms/core 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/dist/assets/events.d.ts +27 -21
  2. package/dist/assets/file-urls.d.ts +11 -7
  3. package/dist/assets/file-urls.js +14 -4
  4. package/dist/assets/file-urls.js.map +1 -1
  5. package/dist/assets/index.d.ts +4 -4
  6. package/dist/assets/index.js +3 -3
  7. package/dist/assets/ports.d.ts +34 -15
  8. package/dist/assets/service.d.ts +30 -14
  9. package/dist/assets/service.js +230 -56
  10. package/dist/assets/service.js.map +1 -1
  11. package/dist/automations/actions.js.map +1 -1
  12. package/dist/automations/catalog.js +69 -4
  13. package/dist/automations/catalog.js.map +1 -1
  14. package/dist/automations/community-derivations.js +16 -16
  15. package/dist/automations/community-derivations.js.map +1 -1
  16. package/dist/automations/community-mail.d.ts +34 -4
  17. package/dist/automations/community-mail.js +32 -4
  18. package/dist/automations/community-mail.js.map +1 -1
  19. package/dist/automations/event-permissions.d.ts +12 -0
  20. package/dist/automations/event-permissions.js +38 -0
  21. package/dist/automations/event-permissions.js.map +1 -0
  22. package/dist/automations/index.d.ts +2 -1
  23. package/dist/automations/index.js +2 -1
  24. package/dist/automations/notifications/applicable.d.ts +8 -0
  25. package/dist/automations/notifications/applicable.js +12 -0
  26. package/dist/automations/notifications/applicable.js.map +1 -0
  27. package/dist/automations/notifications/debounce.js +1 -1
  28. package/dist/automations/notifications/debounce.js.map +1 -1
  29. package/dist/automations/notifications/defaults.js +33 -3
  30. package/dist/automations/notifications/defaults.js.map +1 -1
  31. package/dist/automations/notifications/processor.js +3 -3
  32. package/dist/automations/notifications/processor.js.map +1 -1
  33. package/dist/automations/notifications/rules/content.js +60 -3
  34. package/dist/automations/notifications/rules/content.js.map +1 -1
  35. package/dist/automations/notifications/rules/discussion.js +29 -29
  36. package/dist/automations/notifications/rules/discussion.js.map +1 -1
  37. package/dist/automations/notifications/rules/entitlements.js +36 -9
  38. package/dist/automations/notifications/rules/entitlements.js.map +1 -1
  39. package/dist/automations/notifications/rules/events.d.ts +9 -0
  40. package/dist/automations/notifications/rules/events.js +27 -0
  41. package/dist/automations/notifications/rules/events.js.map +1 -0
  42. package/dist/automations/notifications/rules/organizations.js +17 -10
  43. package/dist/automations/notifications/rules/organizations.js.map +1 -1
  44. package/dist/automations/notifications/rules/recipients.js +0 -1
  45. package/dist/automations/notifications/rules/recipients.js.map +1 -1
  46. package/dist/automations/notifications/service.d.ts +8 -6
  47. package/dist/automations/notifications/service.js +13 -13
  48. package/dist/automations/notifications/service.js.map +1 -1
  49. package/dist/automations/notifications/types.js +94 -7
  50. package/dist/automations/notifications/types.js.map +1 -1
  51. package/dist/automations/ports.d.ts +30 -31
  52. package/dist/automations/service.d.ts +20 -17
  53. package/dist/automations/service.js +91 -62
  54. package/dist/automations/service.js.map +1 -1
  55. package/dist/content/event-sight.d.ts +11 -0
  56. package/dist/content/event-sight.js +11 -0
  57. package/dist/content/event-sight.js.map +1 -0
  58. package/dist/content/events.d.ts +413 -72
  59. package/dist/content/events.js +34 -1
  60. package/dist/content/events.js.map +1 -1
  61. package/dist/content/index.d.ts +6 -4
  62. package/dist/content/index.js +3 -1
  63. package/dist/content/jobs.d.ts +16 -0
  64. package/dist/content/jobs.js +37 -0
  65. package/dist/content/jobs.js.map +1 -0
  66. package/dist/content/model.d.ts +2 -2
  67. package/dist/content/notification-categories.d.ts +5 -4
  68. package/dist/content/notification-categories.js +15 -6
  69. package/dist/content/notification-categories.js.map +1 -1
  70. package/dist/content/ports.d.ts +113 -68
  71. package/dist/content/service.d.ts +99 -67
  72. package/dist/content/service.js +680 -139
  73. package/dist/content/service.js.map +1 -1
  74. package/dist/content/titles.d.ts +6 -3
  75. package/dist/content/titles.js +37 -24
  76. package/dist/content/titles.js.map +1 -1
  77. package/dist/content/types.d.ts +2 -2
  78. package/dist/discussion/authors.d.ts +4 -2
  79. package/dist/discussion/authors.js +9 -5
  80. package/dist/discussion/authors.js.map +1 -1
  81. package/dist/discussion/chats.d.ts +56 -37
  82. package/dist/discussion/chats.js +393 -161
  83. package/dist/discussion/chats.js.map +1 -1
  84. package/dist/discussion/events.d.ts +77 -39
  85. package/dist/discussion/events.js +41 -3
  86. package/dist/discussion/events.js.map +1 -1
  87. package/dist/discussion/index.d.ts +8 -5
  88. package/dist/discussion/index.js +5 -3
  89. package/dist/discussion/live.d.ts +106 -0
  90. package/dist/discussion/live.js +642 -0
  91. package/dist/discussion/live.js.map +1 -0
  92. package/dist/discussion/mentions.js +2 -2
  93. package/dist/discussion/mentions.js.map +1 -1
  94. package/dist/discussion/ports.d.ts +182 -100
  95. package/dist/discussion/ports.js.map +1 -1
  96. package/dist/discussion/posts.d.ts +45 -39
  97. package/dist/discussion/posts.js +268 -154
  98. package/dist/discussion/posts.js.map +1 -1
  99. package/dist/discussion/rules.d.ts +18 -0
  100. package/dist/discussion/rules.js +22 -0
  101. package/dist/discussion/rules.js.map +1 -0
  102. package/dist/discussion/service.d.ts +37 -31
  103. package/dist/discussion/service.js +252 -188
  104. package/dist/discussion/service.js.map +1 -1
  105. package/dist/discussion/subscriptions.d.ts +8 -8
  106. package/dist/discussion/subscriptions.js +29 -13
  107. package/dist/discussion/subscriptions.js.map +1 -1
  108. package/dist/discussion/titles.d.ts +4 -2
  109. package/dist/discussion/titles.js +27 -13
  110. package/dist/discussion/titles.js.map +1 -1
  111. package/dist/entitlements/events.d.ts +20 -20
  112. package/dist/entitlements/index.d.ts +4 -4
  113. package/dist/entitlements/index.js +2 -2
  114. package/dist/entitlements/model.d.ts +2 -2
  115. package/dist/entitlements/ports.d.ts +65 -39
  116. package/dist/entitlements/rules.d.ts +3 -6
  117. package/dist/entitlements/rules.js +5 -10
  118. package/dist/entitlements/rules.js.map +1 -1
  119. package/dist/entitlements/service.d.ts +40 -28
  120. package/dist/entitlements/service.js +370 -163
  121. package/dist/entitlements/service.js.map +1 -1
  122. package/dist/identity/devices.d.ts +3 -3
  123. package/dist/identity/devices.js +7 -5
  124. package/dist/identity/devices.js.map +1 -1
  125. package/dist/identity/events.d.ts +2 -2
  126. package/dist/identity/index.d.ts +2 -2
  127. package/dist/identity/index.js +2 -2
  128. package/dist/identity/notification-settings.d.ts +1 -1
  129. package/dist/identity/notification-settings.js +3 -2
  130. package/dist/identity/notification-settings.js.map +1 -1
  131. package/dist/identity/ports.d.ts +7 -7
  132. package/dist/identity/service.d.ts +8 -7
  133. package/dist/identity/service.js +17 -13
  134. package/dist/identity/service.js.map +1 -1
  135. package/dist/integrations/events.d.ts +3 -3
  136. package/dist/integrations/ports.d.ts +9 -8
  137. package/dist/integrations/service.d.ts +9 -8
  138. package/dist/integrations/service.js +33 -16
  139. package/dist/integrations/service.js.map +1 -1
  140. package/dist/organizations/default-roles.d.ts +22 -0
  141. package/dist/organizations/default-roles.js +89 -0
  142. package/dist/organizations/default-roles.js.map +1 -0
  143. package/dist/organizations/effective-permissions.d.ts +45 -0
  144. package/dist/organizations/effective-permissions.js +113 -0
  145. package/dist/organizations/effective-permissions.js.map +1 -0
  146. package/dist/organizations/events.d.ts +320 -181
  147. package/dist/organizations/events.js +90 -18
  148. package/dist/organizations/events.js.map +1 -1
  149. package/dist/organizations/index.d.ts +14 -10
  150. package/dist/organizations/index.js +7 -3
  151. package/dist/organizations/members.d.ts +66 -8
  152. package/dist/organizations/members.js.map +1 -1
  153. package/dist/organizations/notification-catalog.d.ts +31 -0
  154. package/dist/organizations/notification-catalog.js +32 -0
  155. package/dist/organizations/notification-catalog.js.map +1 -0
  156. package/dist/organizations/notification-categories.d.ts +6 -4
  157. package/dist/organizations/notification-categories.js +25 -10
  158. package/dist/organizations/notification-categories.js.map +1 -1
  159. package/dist/organizations/org-roles.d.ts +33 -0
  160. package/dist/organizations/org-roles.js +49 -0
  161. package/dist/organizations/org-roles.js.map +1 -0
  162. package/dist/organizations/permissions.d.ts +311 -0
  163. package/dist/organizations/permissions.js +327 -0
  164. package/dist/organizations/permissions.js.map +1 -0
  165. package/dist/organizations/ports.d.ts +184 -65
  166. package/dist/organizations/service.d.ts +102 -35
  167. package/dist/organizations/service.js +1245 -328
  168. package/dist/organizations/service.js.map +1 -1
  169. package/dist/organizations/testing.d.ts +18 -0
  170. package/dist/organizations/testing.js +37 -0
  171. package/dist/organizations/testing.js.map +1 -0
  172. package/dist/organizations/types.d.ts +5 -16
  173. package/dist/progress/events.d.ts +14 -14
  174. package/dist/progress/index.d.ts +2 -2
  175. package/dist/progress/ports.d.ts +11 -3
  176. package/dist/progress/service.d.ts +6 -5
  177. package/dist/progress/service.js +23 -8
  178. package/dist/progress/service.js.map +1 -1
  179. package/dist/reporting/admin/index.d.ts +6 -4
  180. package/dist/reporting/admin/model.d.ts +36 -3
  181. package/dist/reporting/admin/ports.d.ts +39 -14
  182. package/dist/reporting/admin/service.d.ts +32 -11
  183. package/dist/reporting/admin/service.js +102 -18
  184. package/dist/reporting/admin/service.js.map +1 -1
  185. package/dist/reporting/events/index.d.ts +27 -0
  186. package/dist/reporting/events/index.js +20 -0
  187. package/dist/reporting/events/index.js.map +1 -0
  188. package/dist/reporting/member/calendar.d.ts +11 -0
  189. package/dist/reporting/member/calendar.js +75 -0
  190. package/dist/reporting/member/calendar.js.map +1 -0
  191. package/dist/reporting/member/index.d.ts +3 -2
  192. package/dist/reporting/member/model.d.ts +26 -5
  193. package/dist/reporting/member/ports.d.ts +48 -29
  194. package/dist/reporting/member/service.d.ts +56 -29
  195. package/dist/reporting/member/service.js +328 -113
  196. package/dist/reporting/member/service.js.map +1 -1
  197. package/dist/shared/actor.d.ts +22 -3
  198. package/dist/shared/errors.d.ts +2 -1
  199. package/dist/shared/errors.js +3 -1
  200. package/dist/shared/errors.js.map +1 -1
  201. package/dist/shared/id.d.ts +4 -0
  202. package/dist/shared/id.js +4 -0
  203. package/dist/shared/id.js.map +1 -1
  204. package/dist/shared/logger.d.ts +2 -1
  205. package/dist/shared/logger.js +10 -1
  206. package/dist/shared/logger.js.map +1 -1
  207. package/dist/shared/ports.d.ts +25 -5
  208. package/dist/types/content.d.ts +2 -2
  209. package/dist/types/discussion.d.ts +4 -5
  210. package/dist/types/email-templates.d.ts +22 -3
  211. package/dist/types/entitlements.d.ts +2 -2
  212. package/dist/types/identity.d.ts +13 -16
  213. package/dist/types/index.d.ts +9 -9
  214. package/dist/types/index.js +2 -1
  215. package/dist/types/notifications.d.ts +4 -4
  216. package/dist/types/organizations.d.ts +3 -2
  217. package/dist/types/ports.d.ts +8 -1
  218. package/dist/types/ports.js +7 -1
  219. package/dist/types/ports.js.map +1 -0
  220. package/dist/types/schemas/assets.d.ts +5 -5
  221. package/dist/types/schemas/assets.js +4 -3
  222. package/dist/types/schemas/assets.js.map +1 -1
  223. package/dist/types/schemas/content.d.ts +537 -13
  224. package/dist/types/schemas/content.js +201 -7
  225. package/dist/types/schemas/content.js.map +1 -1
  226. package/dist/types/schemas/discussion.d.ts +282 -59
  227. package/dist/types/schemas/discussion.js +77 -3
  228. package/dist/types/schemas/discussion.js.map +1 -1
  229. package/dist/types/schemas/entitlements.d.ts +14 -1
  230. package/dist/types/schemas/entitlements.js +11 -4
  231. package/dist/types/schemas/entitlements.js.map +1 -1
  232. package/dist/types/schemas/index.d.ts +5 -5
  233. package/dist/types/schemas/index.js +5 -5
  234. package/dist/types/schemas/notifications.d.ts +13 -12
  235. package/dist/types/schemas/notifications.js +3 -2
  236. package/dist/types/schemas/notifications.js.map +1 -1
  237. package/dist/types/schemas/organizations.d.ts +96 -60
  238. package/dist/types/schemas/organizations.js +78 -27
  239. package/dist/types/schemas/organizations.js.map +1 -1
  240. package/dist/types/schemas/progress.d.ts +3 -4
  241. package/dist/types/schemas/progress.js +0 -1
  242. package/dist/types/schemas/progress.js.map +1 -1
  243. package/package.json +1 -1
  244. package/dist/organizations/access-scopes.d.ts +0 -19
  245. package/dist/organizations/access-scopes.js +0 -89
  246. package/dist/organizations/access-scopes.js.map +0 -1
  247. package/dist/organizations/roles.d.ts +0 -19
  248. package/dist/organizations/roles.js +0 -82
  249. package/dist/organizations/roles.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ports.js","names":[],"sources":["../../src/discussion/ports.ts"],"sourcesContent":["// discussion context — ports.\n// Inbound: the use cases the service implements.\n// Outbound: the persistence contract the repository fulfils.\nimport type {\n Activity,\n Chat,\n ChatLastMessage,\n ChatUser,\n DigestRun,\n FlaggedComment,\n JsonValue,\n MentionItemType,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Post,\n PostFlag,\n PostFlagResolution,\n PostStatus,\n PostView,\n SubjectRef,\n Subscription,\n SubscriptionItemType,\n SubscriptionView,\n} from '../types/index.js';\nimport type { CommunityDigest, SpaceDetail } from '../content/index.js';\nimport type { OutboxAppender, UnitOfWork } from '../shared/ports.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { Role } from '../organizations/index.js';\nimport type { Comment, CommentReport } from './model.js';\nimport type {\n CommentsConfig,\n CommentListItem,\n CommentView,\n ListCommentsQuery,\n Page,\n ReactionCounts,\n ReactionEmoji,\n} from './types.js';\n\n/** An activity's comments as this reader may see them, with the config that\n * decided it. */\nexport interface ActivityComments {\n config: CommentsConfig;\n comments: CommentView[];\n}\n\nexport interface CommentReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport interface CommentWithContext {\n comment: Comment;\n courseId: string;\n activityTitle: string;\n}\n\nexport type { Actor } from '../shared/actor.js';\nexport type PostCommentInput = {\n actor: Actor;\n parentId: string | null;\n body: string;\n} & ({ activityId: string; postId?: undefined } | { postId: string; activityId?: undefined });\n\n/** A profile row as the repository loads it, keyed by `org_users.id`. `email`\n * is stripped before comments are served and kept only for the staff list. */\nexport interface AuthorRecord {\n id: string;\n firstName: string | null;\n lastName: string | null;\n email: string;\n image: string | null;\n role: Role;\n displayName: string | null;\n headline: string | null;\n imageAssetOrgId: string | null;\n imageAssetId: string | null;\n}\n\nexport interface ItemTitle {\n title: string;\n excerpt?: string;\n}\n\nexport interface ItemTitles {\n titlesFor(orgId: string, actor: Actor, refs: SubjectRef[]): Promise<Record<string, ItemTitle>>;\n}\n\nexport interface AuthorProfileReader {\n authorsOf(orgId: string, orgUserIds: string[]): Promise<Record<string, AuthorRecord>>;\n}\n\nexport interface DiscussionService {\n /** Post a root comment or a reply. Lands pending where review is required and\n * the poster is not staff. */\n postComment(orgId: string, input: PostCommentInput): Promise<CommentView>;\n /** Author-only. Throws ForbiddenError for anyone else, staff included. */\n edit(orgId: string, commentId: string, actor: Actor, body: string): Promise<CommentView>;\n /** The author, or anyone whose role is staff. */\n remove(\n orgId: string,\n commentId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<CommentView>;\n /** Staff only. Returns the comment to published. */\n restore(\n orgId: string,\n commentId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<CommentView>;\n /** Staff only. Publishes a pending comment. */\n approve(orgId: string, commentId: string, actor: Actor): Promise<Comment>;\n /** Make a comment published, whichever state it is in. Staff only. */\n publish(orgId: string, commentId: string, actor: Actor): Promise<Comment>;\n\n /** The activity's comments as this reader may see them. */\n activityComments(orgId: string, activityId: string, actor: Actor): Promise<ActivityComments>;\n\n /** One page of top-level comments, oldest first, each with its replies. */\n postComments(\n orgId: string,\n postId: string,\n actor: Actor,\n query: PageQuery,\n ): Promise<Page<CommentView>>;\n\n /**\n * The org's comments, filtered. Staff-scoped: rows carry the author's email\n * and a removed comment's body, neither of which a learner may see, so the\n * edge admits only staff.\n *\n * No rule is applied beyond the filters — this is what exists, not what\n * someone is allowed to read.\n */\n listComments(orgId: string, query: ListCommentsQuery): Promise<Page<CommentListItem>>;\n\n setReaction(\n orgId: string,\n commentId: string,\n actor: Actor,\n emoji: ReactionEmoji | null,\n ): Promise<CommentReactions>;\n\n resolveReports(\n orgId: string,\n commentId: string,\n actor: Actor,\n communityId?: string,\n ): Promise<void>;\n\n /** Accepted even on locked comments — a locked activity can still hold\n * something a moderator needs to see. */\n reportComment(\n orgId: string,\n commentId: string,\n actor: Actor,\n reason: string,\n ): Promise<CommentReport>;\n\n /** Plain read of one comment — no rules applied. */\n getComment(orgId: string, commentId: string): Promise<Comment | null>;\n commenterIds(\n orgId: string,\n target: { postId: string } | { activityId: string },\n ): Promise<string[]>;\n}\n\nexport interface ContentAccessReader {\n hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean>;\n}\n\nexport interface ActivityGetter {\n getActivity(orgId: string, activityId: string): Promise<Activity | null>;\n}\n\nexport interface DiscussionRepository extends PostCommentReader, AuthorProfileReader {\n insertComment(orgId: string, comment: Comment): Promise<Comment>;\n findComment(orgId: string, id: string): Promise<Comment | null>;\n commenterIds(\n orgId: string,\n target: { postId: string } | { activityId: string },\n ): Promise<string[]>;\n updateComment(\n orgId: string,\n id: string,\n patch: Partial<Pick<Comment, 'body' | 'status' | 'removedBy' | 'updatedAt'>>,\n ): Promise<Comment | null>;\n /** Every comment on the activity, oldest first, including removed ones. */\n listByActivity(orgId: string, activityId: string): Promise<Comment[]>;\n /** A page of the post's top-level threads the viewer can see, oldest first, with their replies. `total` counts threads. */\n listThreadsByPost(orgId: string, postId: string, query: PostThreadsQuery): Promise<Page<Comment>>;\n\n reactionsOf(\n orgId: string,\n commentIds: string[],\n viewerId: string,\n ): Promise<Record<string, CommentReactions>>;\n setReaction(\n orgId: string,\n commentId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n\n /** Returns null when this person has already reported this comment. */\n insertReport(orgId: string, report: CommentReport): Promise<CommentReport | null>;\n /** Every unresolved report against the given comments. */\n listOpenReports(orgId: string, commentIds: string[]): Promise<CommentReport[]>;\n resolveReportsFor(\n orgId: string,\n commentId: string,\n resolved: {\n resolvedAt: Date;\n resolvedBy: string;\n resolution: 'dismissed' | 'removed';\n },\n ): Promise<void>;\n\n listComments(orgId: string, query: ListCommentsQuery): Promise<Page<CommentWithContext>>;\n}\n\n/** Writes that emit events run through this scope so the row and the outbox\n * entry commit in one transaction. */\nexport interface DiscussionWriteScope {\n discussion: DiscussionRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type DiscussionUnitOfWork = UnitOfWork<DiscussionWriteScope>;\n\nexport interface AuthorRoleReader {\n roleOf(orgId: string, orgUserId: string): Promise<Role | null>;\n rolesOf(orgId: string, orgUserIds: string[]): Promise<Record<string, Role>>;\n}\n\nexport type PostMode = 'draft' | 'submit' | 'schedule';\n\nexport interface CreatePostInput {\n communityId: string;\n spaceId: string;\n title: string;\n body: string;\n topicId: string | null;\n extra: JsonValue;\n coverAssetId: string | null;\n mode: PostMode;\n scheduledAt: Date | null;\n}\n\nexport interface UpdatePostInput {\n title?: string;\n body?: string;\n topicId?: string | null;\n extra?: JsonValue;\n coverAssetId?: string | null;\n}\n\nexport type PostPatch = Partial<\n Pick<\n Post,\n | 'title'\n | 'body'\n | 'topicId'\n | 'extra'\n | 'coverAssetId'\n | 'status'\n | 'scheduledAt'\n | 'publishedAt'\n | 'removedBy'\n | 'pinned'\n | 'commentsClosed'\n | 'reactionsEnabled'\n >\n>;\n\nexport interface FeedQuery {\n page: number;\n pageSize: number;\n topicId?: string;\n}\n\nexport interface PostReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport type PageQuery = Pick<FeedQuery, 'page' | 'pageSize'>;\n\nexport interface PostThreadsQuery extends PageQuery {\n viewerId: string;\n seesHeld: boolean;\n}\n\nexport interface BookmarkQuery extends PageQuery {\n visibleStatuses: PostStatus[];\n}\n\nexport const MODERATION_QUEUE_LIMIT = 100;\n\nexport interface SpaceModerationQueue {\n pending: PostView[];\n flags: PostFlag[];\n commentFlags: FlaggedComment[];\n}\n\nexport interface PostCommentReader {\n listOpenFlagsForSpace(orgId: string, spaceId: string, limit: number): Promise<FlaggedComment[]>;\n publishedCommentCounts(orgId: string, postIds: string[]): Promise<Record<string, number>>;\n}\n\nexport interface PublishedPost {\n postId: string;\n spaceId: string;\n title: string;\n orgUserId: string;\n commentCount: number;\n}\n\nexport interface PostService {\n visibleTitles(orgId: string, actor: Actor, postIds: string[]): Promise<Record<string, string>>;\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]>;\n create(orgId: string, actor: Actor, input: CreatePostInput): Promise<PostView>;\n update(orgId: string, postId: string, actor: Actor, patch: UpdatePostInput): Promise<PostView>;\n submit(orgId: string, postId: string, actor: Actor): Promise<PostView>;\n schedule(orgId: string, postId: string, actor: Actor, scheduledAt: Date): Promise<PostView>;\n unschedule(orgId: string, postId: string, actor: Actor): Promise<PostView>;\n publishScheduled(orgId: string, postId: string, now: Date): Promise<void>;\n approve(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;\n remove(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;\n restore(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;\n pin(orgId: string, postId: string, actor: Actor): Promise<PostView>;\n unpin(orgId: string, postId: string, actor: Actor): Promise<PostView>;\n closeComments(orgId: string, postId: string, actor: Actor): Promise<PostView>;\n openComments(orgId: string, postId: string, actor: Actor): Promise<PostView>;\n setReactions(orgId: string, postId: string, actor: Actor, enabled: boolean): Promise<PostView>;\n setReaction(\n orgId: string,\n postId: string,\n actor: Actor,\n emoji: ReactionEmoji | null,\n ): Promise<PostReactions>;\n bookmark(orgId: string, postId: string, actor: Actor): Promise<void>;\n unbookmark(orgId: string, postId: string, actor: Actor): Promise<void>;\n flag(orgId: string, postId: string, actor: Actor, reason: string): Promise<PostFlag>;\n resolveFlags(\n orgId: string,\n postId: string,\n actor: Actor,\n resolution: Extract<PostFlagResolution, 'dismissed'>,\n communityId?: string,\n ): Promise<void>;\n spaceFeed(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n query: FeedQuery,\n ): Promise<Page<PostView>>;\n memberFeed(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n ): Promise<Page<PostView>>;\n listOwn(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<PostView>>;\n listBookmarked(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<PostView>>;\n /** Signed URL of the post's cover, for whoever can read the post. */\n coverFile(orgId: string, actor: Actor, postId: string): Promise<string | null>;\n getPost(orgId: string, actor: Actor, postId: string): Promise<PostView>;\n visible(orgId: string, actor: Actor, postId: string): Promise<{ post: Post; space: SpaceDetail }>;\n find(orgId: string, postId: string): Promise<Post | null>;\n moderationQueue(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceModerationQueue>;\n}\n\nexport interface PostGuard {\n status: PostStatus;\n scheduledAt?: Date;\n dueBy?: Date;\n}\n\nexport interface PostRepository {\n insert(orgId: string, post: Post): Promise<Post>;\n update(orgId: string, id: string, patch: PostPatch): Promise<Post | null>;\n updateIf(orgId: string, id: string, guard: PostGuard, patch: PostPatch): Promise<Post | null>;\n find(orgId: string, id: string): Promise<Post | null>;\n findMany(orgId: string, ids: string[]): Promise<Post[]>;\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]>;\n listPublished(orgId: string, spaceIds: string[], query: FeedQuery): Promise<Page<Post>>;\n listByAuthor(\n orgId: string,\n orgUserId: string,\n spaceIds: string[],\n query: PageQuery,\n ): Promise<Page<Post>>;\n listBookmarked(\n orgId: string,\n orgUserId: string,\n spaceIds: string[],\n query: BookmarkQuery,\n ): Promise<Page<Post>>;\n listPending(orgId: string, spaceId: string, limit: number): Promise<Post[]>;\n reactionsOf(\n orgId: string,\n postIds: string[],\n viewerId: string,\n ): Promise<Record<string, PostReactions>>;\n setReaction(\n orgId: string,\n postId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n bookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;\n unbookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;\n bookmarkedAmong(orgId: string, postIds: string[], orgUserId: string): Promise<string[]>;\n insertFlag(orgId: string, flag: PostFlag): Promise<PostFlag | null>;\n findFlag(orgId: string, postId: string, orgUserId: string): Promise<PostFlag | null>;\n listOpenFlags(orgId: string, spaceId: string, limit: number): Promise<PostFlag[]>;\n resolveFlags(\n orgId: string,\n postId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: PostFlagResolution },\n ): Promise<void>;\n}\n\nexport interface PostWriteScope {\n posts: PostRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type PostUnitOfWork = UnitOfWork<PostWriteScope>;\n\nexport interface DigestScheduleReader {\n communitiesWithDigest(): Promise<CommunityDigest[]>;\n}\n\nexport interface DigestRunRepository {\n listRuns(): Promise<DigestRun[]>;\n advance(orgId: string, communityId: string, dueAt: Date): Promise<boolean>;\n}\n\nexport interface DigestWriteScope {\n digests: DigestRunRepository;\n outbox: OutboxAppender;\n}\nexport type DigestUnitOfWork = UnitOfWork<DigestWriteScope>;\n\nexport interface DigestService {\n openDue(now: Date): Promise<void>;\n}\n\nexport interface StartDirectInput {\n communityId: string;\n orgUserId: string;\n body: string | null;\n assetIds: string[];\n}\n\nexport interface StartGroupInput {\n communityId: string;\n orgUserIds: string[];\n}\n\nexport interface SendMessageInput {\n body: string;\n replyToId: string | null;\n assetIds: string[];\n}\n\nexport interface ChatListItem {\n chat: Chat;\n participantIds: string[];\n lastReadMessageId: string | null;\n subscription: SubscriptionView | null;\n unread: number;\n lastMessage: ChatLastMessage | null;\n}\n\nexport type ChatInboxRow = Omit<ChatListItem, 'subscription'>;\n\nexport interface ChatUnread {\n count: number;\n latest: { messageId: string; senderOrgUserId: string; body: string } | null;\n}\n\nexport interface MessageReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport interface ChatSummary {\n chat: Chat;\n participantIds: string[];\n}\n\nexport interface MessageQueueEntry {\n message: MessageView;\n flags: MessageFlag[];\n chat: Chat;\n participantIds: string[];\n}\n\nexport interface MessageQueue {\n entries: MessageQueueEntry[];\n}\n\nexport interface ChatService {\n startDirect(orgId: string, actor: Actor, input: StartDirectInput): Promise<Chat>;\n startGroup(orgId: string, actor: Actor, input: StartGroupInput): Promise<Chat>;\n addParticipant(orgId: string, actor: Actor, chatId: string, orgUserId: string): Promise<Chat>;\n leave(orgId: string, actor: Actor, chatId: string): Promise<Chat>;\n accept(orgId: string, actor: Actor, chatId: string): Promise<Chat>;\n send(orgId: string, actor: Actor, chatId: string, input: SendMessageInput): Promise<MessageView>;\n editMessage(orgId: string, actor: Actor, messageId: string, body: string): Promise<MessageView>;\n deleteMessage(orgId: string, actor: Actor, messageId: string): Promise<MessageView>;\n setReaction(\n orgId: string,\n actor: Actor,\n messageId: string,\n emoji: ReactionEmoji | null,\n ): Promise<MessageReactions>;\n markRead(orgId: string, actor: Actor, chatId: string, messageId: string): Promise<void>;\n block(orgId: string, actor: Actor, communityId: string, orgUserId: string): Promise<void>;\n unblock(orgId: string, actor: Actor, communityId: string, orgUserId: string): Promise<void>;\n flagMessage(orgId: string, actor: Actor, messageId: string, reason: string): Promise<MessageFlag>;\n resolveMessageFlags(\n orgId: string,\n actor: Actor,\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId?: string,\n ): Promise<void>;\n lock(orgId: string, actor: Actor, chatId: string, communityId?: string): Promise<Chat>;\n unlock(orgId: string, actor: Actor, chatId: string, communityId?: string): Promise<Chat>;\n listChats(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<ChatListItem>>;\n chatSummary(\n orgId: string,\n chatId: string,\n ): Promise<{ chat: Chat; participants: ChatUser[] } | null>;\n latestMessageBody(orgId: string, chatId: string): Promise<string | null>;\n unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;\n findMessage(orgId: string, messageId: string): Promise<Message | null>;\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;\n titlesFor(orgId: string, actor: Actor, chatIds: string[]): Promise<Record<string, string>>;\n messages(\n orgId: string,\n actor: Actor,\n chatId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n ): Promise<Page<MessageView>>;\n /** Signed URL of a message attachment: for the chat's participants while the\n * message stands (staff also after deletion), and for staff while it is flagged. */\n attachmentFile(\n orgId: string,\n actor: Actor,\n chatId: string,\n messageId: string,\n assetId: string,\n ): Promise<string | null>;\n messageQueue(orgId: string, actor: Actor, communityId: string): Promise<MessageQueue>;\n}\n\nexport interface ChatRepository {\n lockDirectPair(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherOrgUserId: string,\n ): Promise<void>;\n findDirect(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherOrgUserId: string,\n ): Promise<Chat | null>;\n insertChat(orgId: string, chat: Chat): Promise<Chat>;\n updateChat(\n orgId: string,\n id: string,\n patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>,\n ): Promise<Chat | null>;\n findChat(orgId: string, id: string): Promise<Chat | null>;\n listForUser(\n orgId: string,\n communityId: string,\n orgUserId: string,\n query: PageQuery,\n ): Promise<Page<ChatInboxRow>>;\n participants(orgId: string, chatId: string): Promise<ChatUser[]>;\n join(orgId: string, user: ChatUser): Promise<void>;\n markLeft(orgId: string, chatId: string, orgUserId: string, leftAt: Date): Promise<void>;\n setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<void>;\n unreadFor(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;\n insertMessage(orgId: string, message: Message): Promise<Message>;\n updateMessage(\n orgId: string,\n id: string,\n patch: Partial<Pick<Message, 'body' | 'editedAt' | 'deletedAt'>>,\n ): Promise<Message | null>;\n findMessage(orgId: string, id: string): Promise<Message | null>;\n listMessages(\n orgId: string,\n chatId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n ): Promise<Page<Message>>;\n attach(orgId: string, communityId: string, messageId: string, assetIds: string[]): Promise<void>;\n attachmentsOf(orgId: string, messageIds: string[]): Promise<Record<string, string[]>>;\n blockedWith(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherIds: string[],\n ): Promise<string[]>;\n setReaction(\n orgId: string,\n communityId: string,\n messageId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n reactionsOf(\n orgId: string,\n messageIds: string[],\n viewerId: string,\n ): Promise<Record<string, MessageReactions>>;\n block(\n orgId: string,\n communityId: string,\n blockerOrgUserId: string,\n blockedOrgUserId: string,\n ): Promise<void>;\n unblock(\n orgId: string,\n communityId: string,\n blockerOrgUserId: string,\n blockedOrgUserId: string,\n ): Promise<void>;\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;\n anyBlockedAmong(orgId: string, communityId: string, orgUserIds: string[]): Promise<boolean>;\n insertFlag(orgId: string, flag: MessageFlag): Promise<MessageFlag | null>;\n findFlag(orgId: string, messageId: string, orgUserId: string): Promise<MessageFlag | null>;\n listOpenFlags(orgId: string, communityId: string, limit?: number): Promise<MessageFlag[]>;\n messagesByIds(orgId: string, ids: string[]): Promise<Message[]>;\n chatSummaries(orgId: string, chatIds: string[]): Promise<ChatSummary[]>;\n resolveFlags(\n orgId: string,\n messageId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: MessageFlagResolution },\n ): Promise<void>;\n resolveOpenFlagsForChat(\n orgId: string,\n chatId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: MessageFlagResolution },\n ): Promise<void>;\n}\n\nexport interface ChatWriteScope {\n chats: ChatRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type ChatUnitOfWork = UnitOfWork<ChatWriteScope>;\n\nexport type SubscriptionInput =\n { state: 'following' } | { state: 'muted'; mutedUntil?: Date | null } | { state: 'none' };\n\nexport type MuteInput = Exclude<SubscriptionInput, { state: 'following' }>;\n\nexport interface SubscriptionService {\n setPost(orgId: string, actor: Actor, postId: string, input: SubscriptionInput): Promise<void>;\n setSpace(orgId: string, actor: Actor, spaceId: string, input: MuteInput): Promise<void>;\n setChat(orgId: string, actor: Actor, chatId: string, input: MuteInput): Promise<void>;\n viewerSubscriptions(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemIds: string[],\n at: Date,\n ): Promise<Record<string, SubscriptionView>>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n mutedAmong(orgId: string, item: SubjectRef, orgUserIds: string[], at: Date): Promise<string[]>;\n itemSubscriptions(orgId: string, item: SubjectRef): Promise<Subscription[]>;\n}\n\nexport interface SubscriptionsRepository {\n upsert(orgId: string, subscription: Subscription): Promise<void>;\n remove(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemId: string,\n ): Promise<void>;\n forViewer(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemIds: string[],\n ): Promise<Subscription[]>;\n ofItem(orgId: string, itemType: SubscriptionItemType, itemId: string): Promise<Subscription[]>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n mutedAmong(\n orgId: string,\n itemType: SubscriptionItemType,\n itemId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<string[]>;\n}\n\nexport interface MentionsRepository {\n ofItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<string[]>;\n forItems(\n orgId: string,\n itemType: MentionItemType,\n itemIds: string[],\n ): Promise<Record<string, string[]>>;\n sync(\n orgId: string,\n itemType: MentionItemType,\n itemId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<string[]>;\n removeItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<void>;\n}\n"],"mappings":";AA6SA,MAAa,yBAAyB"}
1
+ {"version":3,"file":"ports.js","names":[],"sources":["../../src/discussion/ports.ts"],"sourcesContent":["// discussion context — ports.\n// Inbound: the use cases the service implements.\n// Outbound: the persistence contract the repository fulfils.\nimport type {\n Activity,\n Chat,\n ChatLastMessage,\n ChatLiveEvent,\n ChatUnreadSummary,\n ChatUser,\n DigestRun,\n FlaggedComment,\n FlaggedCommentView,\n JsonValue,\n LiveMessage,\n MentionItemType,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Post,\n PostFlag,\n PostFlagResolution,\n PostStatus,\n PostView,\n SubjectRef,\n Subscription,\n SubscriptionItemType,\n SubscriptionView,\n} from '../types/index.js';\nimport type { CommunityDigest, DmSettings, SpaceDetail } from '../content/index.js';\nimport type { OutboxAppender, UnitOfWork } from '../shared/ports.js';\nimport type { Actor, EffectivePermissions } from '../shared/actor.js';\nimport type { Comment, CommentAuthor, CommentReport } from './model.js';\nimport type {\n CommentsConfig,\n CommentListItem,\n CommentView,\n ListCommentsQuery,\n Page,\n ReactionCounts,\n ReactionEmoji,\n} from './types.js';\n\n/** An activity's comments as this reader may see them, with the config that\n * decided it. */\nexport interface ActivityComments {\n config: CommentsConfig;\n comments: CommentView[];\n}\n\nexport interface CommentReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport interface CommentWithContext {\n comment: Comment;\n courseId: string;\n activityTitle: string;\n}\n\nexport type { Actor } from '../shared/actor.js';\nexport type PostCommentInput = {\n parentId: string | null;\n body: string;\n} & ({ activityId: string; postId?: undefined } | { postId: string; activityId?: undefined });\n\n/** A profile row as the repository loads it, keyed by `org_users.id`. `email`\n * is stripped before comments are served and kept only for the moderation list. */\nexport interface AuthorRecord {\n id: string;\n firstName: string | null;\n lastName: string | null;\n email: string;\n image: string | null;\n displayName: string | null;\n headline: string | null;\n imageAssetOrgId: string | null;\n imageAssetId: string | null;\n}\n\nexport interface ItemTitle {\n title: string;\n excerpt?: string;\n}\n\nexport interface ItemTitles {\n titlesFor(refs: SubjectRef[], actor: Actor): Promise<Record<string, ItemTitle>>;\n}\n\nexport interface AuthorProfileReader {\n authorsOf(orgId: string, orgUserIds: string[]): Promise<Record<string, AuthorRecord>>;\n}\n\n/** Where a comment must be for a moderation call: in one community, on a\n * lesson, or anywhere the actor can reach. */\nexport type CommentWithin = { communityId: string } | 'lesson' | 'any';\n\nexport interface DiscussionService {\n /** Post a root comment or a reply. Lands pending where review is required and\n * the poster does not skip review. */\n postComment(input: PostCommentInput, actor: Actor): Promise<CommentView>;\n /** Reply to a lesson comment as a moderator of its course; a reply to a reply\n * attaches to its root. */\n replyToComment(commentId: string, body: string, actor: Actor): Promise<CommentView>;\n /** Author-only. Throws ForbiddenError for anyone else, moderators included. */\n edit(commentId: string, body: string, actor: Actor): Promise<CommentView>;\n /** The author, or a moderator of where the comment is. */\n remove(commentId: string, within: CommentWithin, actor: Actor): Promise<CommentView>;\n /** A moderator of where the comment is. Returns the comment to published. */\n restore(commentId: string, within: CommentWithin, actor: Actor): Promise<CommentView>;\n /** A moderator of where the comment is. Publishes a pending comment. */\n approve(commentId: string, actor: Actor): Promise<Comment>;\n /** Make a comment published, whichever state it is in. A moderator of where\n * the comment is. */\n publish(commentId: string, within: CommentWithin, actor: Actor): Promise<Comment>;\n\n /** The activity's comments as this reader may see them. */\n activityComments(activityId: string, actor: Actor): Promise<ActivityComments>;\n\n /** One page of top-level comments, oldest first, each with its replies. */\n postComments(postId: string, query: PageQuery, actor: Actor): Promise<Page<CommentView>>;\n\n /** The lesson comments of the courses the actor moderates, filtered. Rows\n * carry the author's email and a removed comment's body. */\n listComments(query: ListCommentsQuery, actor: Actor): Promise<Page<CommentListItem>>;\n\n setReaction(\n commentId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<CommentReactions>;\n\n resolveReports(commentId: string, within: CommentWithin, actor: Actor): Promise<void>;\n\n /** Accepted even on locked comments — a locked activity can still hold\n * something a moderator needs to see. */\n reportComment(commentId: string, reason: string, actor: Actor): Promise<CommentReport>;\n\n /** Plain read of one comment — no rules applied. */\n getComment(orgId: string, commentId: string): Promise<Comment | null>;\n commenterIds(\n orgId: string,\n target: { postId: string } | { activityId: string },\n ): Promise<string[]>;\n}\n\nexport interface ContentAccessReader {\n hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean>;\n}\n\nexport interface ActivityGetter {\n getActivity(activityId: string, actor: Actor): Promise<Activity | null>;\n}\n\nexport interface SpaceReader {\n getSpace(spaceId: string, actor: Actor): Promise<SpaceDetail | null>;\n getDmSettings(communityId: string, actor: Actor): Promise<DmSettings | null>;\n}\n\nexport interface OpenableSpace {\n id: string;\n communityId: string;\n accessRule: SpaceDetail['accessRule'];\n}\n\nexport interface CommunityAccess {\n isCommunityMember(communityId: string, actor: Actor): Promise<boolean>;\n canOpenSpace(spaceId: string, actor: Actor): Promise<boolean>;\n canOpen(space: OpenableSpace, actor: Actor): Promise<boolean>;\n openSpaceIds(communityId: string, actor: Actor): Promise<string[]>;\n communityMembersAmong(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<string[]>;\n spaceOpenersAmong(orgId: string, spaceId: string, orgUserIds: string[]): Promise<string[]>;\n}\n\nexport interface DiscussionRepository extends PostCommentReader, AuthorProfileReader {\n insertComment(orgId: string, comment: Comment): Promise<Comment>;\n findComment(orgId: string, id: string): Promise<Comment | null>;\n commenterIds(\n orgId: string,\n target: { postId: string } | { activityId: string },\n ): Promise<string[]>;\n updateComment(\n orgId: string,\n id: string,\n patch: Partial<Pick<Comment, 'body' | 'status' | 'removedBy' | 'updatedAt'>>,\n ): Promise<Comment | null>;\n /** Every comment on the activity, oldest first, including removed ones. */\n listByActivity(orgId: string, activityId: string): Promise<Comment[]>;\n /** A page of the post's top-level threads the viewer can see, oldest first, with their replies. `total` counts threads. */\n listThreadsByPost(orgId: string, postId: string, query: PostThreadsQuery): Promise<Page<Comment>>;\n\n reactionsOf(\n orgId: string,\n commentIds: string[],\n viewerId: string,\n ): Promise<Record<string, CommentReactions>>;\n setReaction(\n orgId: string,\n commentId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n\n /** Returns null when this person has already reported this comment. */\n insertReport(orgId: string, report: CommentReport): Promise<CommentReport | null>;\n /** Every unresolved report against the given comments. */\n listOpenReports(orgId: string, commentIds: string[]): Promise<CommentReport[]>;\n resolveReportsFor(\n orgId: string,\n commentId: string,\n resolved: {\n resolvedAt: Date;\n resolvedBy: string;\n resolution: 'dismissed' | 'removed';\n },\n ): Promise<void>;\n\n /** `courseIds` limits the rows to those courses; undefined means every course. */\n listComments(\n orgId: string,\n query: ListCommentsQuery,\n courseIds?: string[],\n ): Promise<Page<CommentWithContext>>;\n}\n\n/** Writes that emit events run through this scope so the row and the outbox\n * entry commit in one transaction. */\nexport interface DiscussionWriteScope {\n discussion: DiscussionRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type DiscussionUnitOfWork = UnitOfWork<DiscussionWriteScope>;\n\nexport interface AuthorPermissionReader {\n permissionsOfMany(\n orgId: string,\n orgUserIds: string[],\n ): Promise<Map<string, EffectivePermissions>>;\n actorFor(orgId: string, orgUserId: string): Promise<Actor | null>;\n systemActor(orgId: string): Actor;\n}\n\nexport type PostMode = 'draft' | 'submit' | 'schedule';\n\nexport interface CreatePostInput {\n communityId: string;\n spaceId: string;\n title: string;\n body: string;\n topicId: string | null;\n extra: JsonValue;\n coverAssetId: string | null;\n mode: PostMode;\n scheduledAt: Date | null;\n}\n\nexport interface UpdatePostInput {\n title?: string;\n body?: string;\n topicId?: string | null;\n extra?: JsonValue;\n coverAssetId?: string | null;\n}\n\nexport type PostPatch = Partial<\n Pick<\n Post,\n | 'title'\n | 'body'\n | 'topicId'\n | 'extra'\n | 'coverAssetId'\n | 'status'\n | 'scheduledAt'\n | 'publishedAt'\n | 'removedBy'\n | 'pinned'\n | 'commentsClosed'\n | 'reactionsEnabled'\n >\n>;\n\nexport interface FeedQuery {\n page: number;\n pageSize: number;\n topicId?: string;\n}\n\nexport interface PostReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport type PageQuery = Pick<FeedQuery, 'page' | 'pageSize'>;\n\nexport interface PostThreadsQuery extends PageQuery {\n viewerId: string;\n seesHeld: boolean;\n}\n\nexport interface BookmarkQuery extends PageQuery {\n visibleStatuses: PostStatus[];\n}\n\nexport const MODERATION_QUEUE_LIMIT = 100;\n\nexport interface SpaceModerationQueue {\n pending: PostView[];\n flags: PostFlag[];\n commentFlags: FlaggedCommentView[];\n}\n\nexport interface PostCommentReader {\n listOpenFlagsForSpace(orgId: string, spaceId: string, limit: number): Promise<FlaggedComment[]>;\n publishedCommentCounts(orgId: string, postIds: string[]): Promise<Record<string, number>>;\n}\n\nexport interface PublishedPost {\n postId: string;\n spaceId: string;\n title: string;\n orgUserId: string;\n commentCount: number;\n}\n\nexport interface PostService {\n visibleTitles(postIds: string[], actor: Actor): Promise<Record<string, string>>;\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]>;\n create(input: CreatePostInput, actor: Actor): Promise<PostView>;\n update(postId: string, patch: UpdatePostInput, actor: Actor): Promise<PostView>;\n submit(postId: string, actor: Actor): Promise<PostView>;\n schedule(postId: string, scheduledAt: Date, actor: Actor): Promise<PostView>;\n unschedule(postId: string, actor: Actor): Promise<PostView>;\n publishScheduled(orgId: string, postId: string, now: Date): Promise<void>;\n approve(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n remove(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n restore(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n pin(postId: string, actor: Actor): Promise<PostView>;\n unpin(postId: string, actor: Actor): Promise<PostView>;\n closeComments(postId: string, actor: Actor): Promise<PostView>;\n openComments(postId: string, actor: Actor): Promise<PostView>;\n setReactions(postId: string, enabled: boolean, actor: Actor): Promise<PostView>;\n setReaction(postId: string, emoji: ReactionEmoji | null, actor: Actor): Promise<PostReactions>;\n bookmark(postId: string, actor: Actor): Promise<void>;\n unbookmark(postId: string, actor: Actor): Promise<void>;\n flag(postId: string, reason: string, actor: Actor): Promise<PostFlag>;\n resolveFlags(\n postId: string,\n resolution: Extract<PostFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void>;\n spaceFeed(\n communityId: string,\n spaceId: string,\n query: FeedQuery,\n actor: Actor,\n ): Promise<Page<PostView>>;\n memberFeed(\n communityId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n actor: Actor,\n ): Promise<Page<PostView>>;\n listOwn(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;\n listBookmarked(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;\n /** Signed URL of the post's cover, for whoever can read the post. */\n coverFile(postId: string, actor: Actor): Promise<string>;\n assetFile(postId: string, assetId: string, actor: Actor): Promise<string>;\n getPost(postId: string, actor: Actor): Promise<PostView>;\n visible(postId: string, actor: Actor): Promise<{ post: Post; space: SpaceDetail }>;\n find(orgId: string, postId: string): Promise<Post | null>;\n moderationQueue(\n communityId: string,\n spaceId: string,\n actor: Actor,\n ): Promise<SpaceModerationQueue>;\n}\n\nexport interface PostGuard {\n status: PostStatus;\n scheduledAt?: Date;\n dueBy?: Date;\n}\n\nexport interface PostRepository {\n insert(orgId: string, post: Post): Promise<Post>;\n update(orgId: string, id: string, patch: PostPatch): Promise<Post | null>;\n updateIf(orgId: string, id: string, guard: PostGuard, patch: PostPatch): Promise<Post | null>;\n find(orgId: string, id: string): Promise<Post | null>;\n findMany(orgId: string, ids: string[]): Promise<Post[]>;\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]>;\n listPublished(orgId: string, spaceIds: string[], query: FeedQuery): Promise<Page<Post>>;\n listByAuthor(\n orgId: string,\n orgUserId: string,\n spaceIds: string[],\n query: PageQuery,\n ): Promise<Page<Post>>;\n listBookmarked(\n orgId: string,\n orgUserId: string,\n spaceIds: string[],\n query: BookmarkQuery,\n ): Promise<Page<Post>>;\n listPending(orgId: string, spaceId: string, limit: number): Promise<Post[]>;\n reactionsOf(\n orgId: string,\n postIds: string[],\n viewerId: string,\n ): Promise<Record<string, PostReactions>>;\n setReaction(\n orgId: string,\n postId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n bookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;\n unbookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;\n bookmarkedAmong(orgId: string, postIds: string[], orgUserId: string): Promise<string[]>;\n insertFlag(orgId: string, flag: PostFlag): Promise<PostFlag | null>;\n findFlag(orgId: string, postId: string, orgUserId: string): Promise<PostFlag | null>;\n listOpenFlags(orgId: string, spaceId: string, limit: number): Promise<PostFlag[]>;\n resolveFlags(\n orgId: string,\n postId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: PostFlagResolution },\n ): Promise<void>;\n}\n\nexport interface PostWriteScope {\n posts: PostRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type PostUnitOfWork = UnitOfWork<PostWriteScope>;\n\nexport interface DigestScheduleReader {\n communitiesWithDigest(): Promise<CommunityDigest[]>;\n}\n\nexport interface DigestRunRepository {\n listRuns(): Promise<DigestRun[]>;\n advance(orgId: string, communityId: string, dueAt: Date): Promise<boolean>;\n}\n\nexport interface DigestWriteScope {\n digests: DigestRunRepository;\n outbox: OutboxAppender;\n}\nexport type DigestUnitOfWork = UnitOfWork<DigestWriteScope>;\n\nexport interface DigestService {\n openDue(now: Date): Promise<void>;\n}\n\nexport interface StartDirectInput {\n communityId: string;\n orgUserId: string;\n body: string | null;\n assetIds: string[];\n}\n\nexport interface StartGroupInput {\n communityId: string;\n orgUserIds: string[];\n}\n\nexport interface SendMessageInput {\n body: string;\n replyToId: string | null;\n assetIds: string[];\n clientId?: string;\n}\n\nexport interface ChatListItem {\n chat: Chat;\n participantIds: string[];\n participants: CommentAuthor[];\n lastReadMessageId: string | null;\n subscription: SubscriptionView | null;\n unread: number;\n lastMessage: ChatLastMessage | null;\n}\n\nexport type ChatInboxRow = Omit<ChatListItem, 'subscription' | 'participants'>;\n\n/** `requests`: true for only the pending requests to the member, false for\n * everything else, omitted for every chat. */\nexport interface ChatListQuery extends PageQuery {\n requests?: boolean;\n}\n\nexport interface ReactionChange {\n inserted: boolean;\n changed: boolean;\n}\n\nexport interface ChatMemberState {\n chatId: string;\n unread: number;\n lastReadMessageId: string | null;\n subscription: SubscriptionView | null;\n}\n\n/** A member's state in each chat they are in in one community, with their\n * unread total there. */\nexport interface ChatMemberStates {\n orgUserId: string;\n unread: ChatUnreadSummary;\n chats: ChatMemberState[];\n}\n\nexport interface LiveChat {\n chat: Chat;\n participantIds: string[];\n participants: CommentAuthor[];\n}\n\n/** What the chat hub reads for each change, as current state. */\nexport interface ChatLiveReads {\n activeParticipants(orgId: string, chatId: string): Promise<string[]>;\n messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]>;\n messageReactions(orgId: string, messageId: string): Promise<ReactionCounts>;\n /** The chat with its active participants and their profiles. */\n liveChat(orgId: string, chatId: string): Promise<LiveChat | null>;\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<ChatMemberStates[]>;\n}\n\nexport interface ChatUnread {\n count: number;\n latest: { messageId: string; senderOrgUserId: string; body: string } | null;\n}\n\nexport interface MessageReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport interface ChatSummary {\n chat: Chat;\n participantIds: string[];\n}\n\nexport interface MessageQueueEntry {\n message: MessageView;\n flags: MessageFlag[];\n chat: Chat;\n participantIds: string[];\n}\n\nexport interface MessageQueue {\n entries: MessageQueueEntry[];\n}\n\nexport interface ChatService extends ChatLiveReads {\n startDirect(input: StartDirectInput, actor: Actor): Promise<Chat>;\n startGroup(input: StartGroupInput, actor: Actor): Promise<Chat>;\n addParticipant(chatId: string, orgUserId: string, actor: Actor): Promise<Chat>;\n leave(chatId: string, actor: Actor): Promise<Chat>;\n accept(chatId: string, actor: Actor): Promise<Chat>;\n send(chatId: string, input: SendMessageInput, actor: Actor): Promise<MessageView>;\n editMessage(messageId: string, body: string, actor: Actor): Promise<MessageView>;\n deleteMessage(messageId: string, actor: Actor): Promise<MessageView>;\n setReaction(\n messageId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<MessageReactions>;\n markRead(chatId: string, messageId: string, actor: Actor): Promise<void>;\n block(communityId: string, orgUserId: string, actor: Actor): Promise<void>;\n unblock(communityId: string, orgUserId: string, actor: Actor): Promise<void>;\n flagMessage(messageId: string, reason: string, actor: Actor): Promise<MessageFlag>;\n resolveMessageFlags(\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void>;\n lock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat>;\n unlock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat>;\n listChats(communityId: string, query: ChatListQuery, actor: Actor): Promise<Page<ChatListItem>>;\n /** One live stream of the member's chat events in the community. */\n watch(communityId: string, actor: Actor): Promise<AsyncIterable<ChatLiveEvent>>;\n chat(chatId: string, actor: Actor): Promise<ChatListItem>;\n directChat(communityId: string, orgUserId: string, actor: Actor): Promise<ChatListItem>;\n unreadSummary(communityId: string, actor: Actor): Promise<ChatUnreadSummary>;\n typing(chatId: string, actor: Actor): Promise<void>;\n /** The messages as every participant sees them: a deleted message without\n * its body, attachments or mentions, and no viewer reaction. */\n messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]>;\n chatSummary(\n orgId: string,\n chatId: string,\n ): Promise<{ chat: Chat; participants: ChatUser[] } | null>;\n latestMessageBody(orgId: string, chatId: string): Promise<string | null>;\n unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;\n findMessage(orgId: string, messageId: string): Promise<Message | null>;\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;\n titlesFor(chatIds: string[], actor: Actor): Promise<Record<string, string>>;\n messages(\n chatId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n actor: Actor,\n ): Promise<Page<MessageView>>;\n /** Signed URL of a message attachment: for the chat's participants while the\n * message stands (moderators also after deletion), and for moderators while\n * it is flagged. */\n attachmentFile(chatId: string, messageId: string, assetId: string, actor: Actor): Promise<string>;\n messageQueue(communityId: string, actor: Actor): Promise<MessageQueue>;\n}\n\nexport interface ChatRepository {\n lockDirectPair(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherOrgUserId: string,\n ): Promise<void>;\n findDirect(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherOrgUserId: string,\n ): Promise<Chat | null>;\n insertChat(orgId: string, chat: Chat): Promise<Chat>;\n updateChat(\n orgId: string,\n id: string,\n patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>,\n ): Promise<Chat | null>;\n findChat(orgId: string, id: string): Promise<Chat | null>;\n listForUser(\n orgId: string,\n communityId: string,\n orgUserId: string,\n query: ChatListQuery,\n ): Promise<Page<ChatInboxRow>>;\n /** The member's inbox row for one chat they are in, or null. */\n inboxRow(orgId: string, chatId: string, orgUserId: string): Promise<ChatInboxRow | null>;\n participants(orgId: string, chatId: string): Promise<ChatUser[]>;\n activeParticipants(orgId: string, chatId: string): Promise<string[]>;\n join(orgId: string, user: ChatUser): Promise<void>;\n markLeft(orgId: string, chatId: string, orgUserId: string, leftAt: Date): Promise<void>;\n /** Moves the read mark to the message only when it is newer (by created\n * time, then id) than the current one. True when it moved. */\n setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<boolean>;\n unreadFor(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;\n /** Unread messages across the member's chats in the community that are\n * neither muted at `at` nor pending requests to them, plus those requests. */\n unreadSummary(\n orgId: string,\n communityId: string,\n orgUserId: string,\n at: Date,\n ): Promise<ChatUnreadSummary>;\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<ChatMemberStates[]>;\n findByClientId(\n orgId: string,\n chatId: string,\n orgUserId: string,\n clientId: string,\n ): Promise<Message | null>;\n insertMessage(orgId: string, message: Message): Promise<Message>;\n updateMessage(\n orgId: string,\n id: string,\n patch: Partial<Pick<Message, 'body' | 'editedAt' | 'deletedAt'>>,\n ): Promise<Message | null>;\n findMessage(orgId: string, id: string): Promise<Message | null>;\n listMessages(\n orgId: string,\n chatId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n ): Promise<Page<Message>>;\n attach(orgId: string, communityId: string, messageId: string, assetIds: string[]): Promise<void>;\n attachmentsOf(orgId: string, messageIds: string[]): Promise<Record<string, string[]>>;\n blockedWith(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherIds: string[],\n ): Promise<string[]>;\n setReaction(\n orgId: string,\n communityId: string,\n messageId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<ReactionChange>;\n reactionsOf(\n orgId: string,\n messageIds: string[],\n viewerId: string,\n ): Promise<Record<string, MessageReactions>>;\n block(\n orgId: string,\n communityId: string,\n blockerOrgUserId: string,\n blockedOrgUserId: string,\n ): Promise<void>;\n unblock(\n orgId: string,\n communityId: string,\n blockerOrgUserId: string,\n blockedOrgUserId: string,\n ): Promise<void>;\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;\n anyBlockedAmong(orgId: string, communityId: string, orgUserIds: string[]): Promise<boolean>;\n insertFlag(orgId: string, flag: MessageFlag): Promise<MessageFlag | null>;\n findFlag(orgId: string, messageId: string, orgUserId: string): Promise<MessageFlag | null>;\n listOpenFlags(orgId: string, communityId: string, limit?: number): Promise<MessageFlag[]>;\n messagesByIds(orgId: string, ids: string[]): Promise<Message[]>;\n chatSummaries(orgId: string, chatIds: string[]): Promise<ChatSummary[]>;\n resolveFlags(\n orgId: string,\n messageId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: MessageFlagResolution },\n ): Promise<void>;\n resolveOpenFlagsForChat(\n orgId: string,\n chatId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: MessageFlagResolution },\n ): Promise<void>;\n}\n\nexport interface ChatWriteScope {\n chats: ChatRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type ChatUnitOfWork = UnitOfWork<ChatWriteScope>;\n\nexport type SubscriptionInput =\n { state: 'following' } | { state: 'muted'; mutedUntil?: Date | null } | { state: 'none' };\n\nexport type MuteInput = Exclude<SubscriptionInput, { state: 'following' }>;\n\nexport interface SubscriptionWriteScope {\n subscriptions: SubscriptionsRepository;\n outbox: OutboxAppender;\n}\nexport type SubscriptionUnitOfWork = UnitOfWork<SubscriptionWriteScope>;\n\nexport interface SubscriptionService {\n setPost(postId: string, input: SubscriptionInput, actor: Actor): Promise<void>;\n setSpace(spaceId: string, input: MuteInput, actor: Actor): Promise<void>;\n setChat(chatId: string, input: MuteInput, actor: Actor): Promise<void>;\n viewerSubscriptions(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemIds: string[],\n at: Date,\n ): Promise<Record<string, SubscriptionView>>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n mutedAmong(orgId: string, item: SubjectRef, orgUserIds: string[], at: Date): Promise<string[]>;\n itemSubscriptions(orgId: string, item: SubjectRef): Promise<Subscription[]>;\n}\n\nexport interface SubscriptionsRepository {\n upsert(orgId: string, subscription: Subscription): Promise<void>;\n remove(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemId: string,\n ): Promise<void>;\n forViewer(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemIds: string[],\n ): Promise<Subscription[]>;\n ofItem(orgId: string, itemType: SubscriptionItemType, itemId: string): Promise<Subscription[]>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n mutedAmong(\n orgId: string,\n itemType: SubscriptionItemType,\n itemId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<string[]>;\n}\n\nexport interface MentionsRepository {\n ofItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<string[]>;\n forItems(\n orgId: string,\n itemType: MentionItemType,\n itemIds: string[],\n ): Promise<Record<string, string[]>>;\n sync(\n orgId: string,\n itemType: MentionItemType,\n itemId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<string[]>;\n removeItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<void>;\n}\n"],"mappings":";AAuTA,MAAa,yBAAyB"}
@@ -3,29 +3,27 @@ import { Logger } from "../types/ports.js";
3
3
  import { SpaceDetail } from "../types/schemas/content.js";
4
4
  import { Post, PostFlag, PostFlagResolution, PostView, ReactionEmoji } from "../types/schemas/discussion.js";
5
5
  import "../types/index.js";
6
- import { Scheduler } from "../shared/ports.js";
6
+ import { Actor } from "../shared/actor.js";
7
+ import { BodyParser, Scheduler } from "../shared/ports.js";
7
8
  import { FileUrls } from "../assets/file-urls.js";
8
- import { AssetFiles, MemberImageReader } from "../assets/ports.js";
9
+ import { AssetFiles, MemberImageReader, MemberUploadReader } from "../assets/ports.js";
9
10
  import "../assets/index.js";
10
- import { Actor } from "../shared/actor.js";
11
- import { CommunityReader } from "../content/ports.js";
12
11
  import "../content/index.js";
13
- import { CommunityAccessReader } from "../entitlements/ports.js";
14
- import "../entitlements/index.js";
15
12
  import "./types.js";
16
- import { AuthorProfileReader, AuthorRoleReader, CreatePostInput, FeedQuery, MentionsRepository, PageQuery, PostCommentReader, PostReactions, PostRepository, PostService, PostUnitOfWork, PublishedPost, SpaceModerationQueue, SubscriptionsRepository, UpdatePostInput } from "./ports.js";
13
+ import { AuthorPermissionReader, AuthorProfileReader, CommunityAccess, CreatePostInput, FeedQuery, MentionsRepository, PageQuery, PostCommentReader, PostReactions, PostRepository, PostService, PostUnitOfWork, PublishedPost, SpaceModerationQueue, SpaceReader, SubscriptionsRepository, UpdatePostInput } from "./ports.js";
17
14
  //#region src/discussion/posts.d.ts
18
15
  type PostServiceParams = {
19
16
  repo: PostRepository;
20
- access: CommunityAccessReader;
21
- content: CommunityReader;
22
- authors: AuthorRoleReader;
17
+ access: CommunityAccess;
18
+ content: Pick<SpaceReader, 'getSpace'>;
19
+ authors: AuthorPermissionReader;
23
20
  profiles: AuthorProfileReader;
24
21
  comments: PostCommentReader;
25
22
  subscriptions: SubscriptionsRepository;
26
23
  mentions: MentionsRepository;
27
- images: MemberImageReader & Pick<AssetFiles, 'signFile'>;
24
+ images: MemberImageReader & MemberUploadReader & Pick<AssetFiles, 'signFile' | 'syncPublicImage'>;
28
25
  fileUrls: FileUrls;
26
+ bodies: BodyParser;
29
27
  uow: PostUnitOfWork;
30
28
  scheduler: Pick<Scheduler, 'at' | 'cancel'>;
31
29
  logger?: Logger;
@@ -41,42 +39,47 @@ declare class PostServiceImpl implements PostService {
41
39
  private readonly mentions;
42
40
  private readonly images;
43
41
  private readonly fileUrls;
42
+ private readonly bodies;
44
43
  private readonly uow;
45
44
  private readonly scheduler;
46
45
  private readonly logger;
47
46
  constructor(params: PostServiceParams);
48
- visibleTitles(orgId: string, actor: Actor, postIds: string[]): Promise<Record<string, string>>;
47
+ visibleTitles(postIds: string[], actor: Actor): Promise<Record<string, string>>;
49
48
  publishedBetween(orgId: string, communityId: string, from: Date, to: Date): Promise<PublishedPost[]>;
50
- create(orgId: string, actor: Actor, input: CreatePostInput): Promise<PostView>;
51
- update(orgId: string, postId: string, actor: Actor, patch: UpdatePostInput): Promise<PostView>;
52
- submit(orgId: string, postId: string, actor: Actor): Promise<PostView>;
53
- schedule(orgId: string, postId: string, actor: Actor, scheduledAt: Date): Promise<PostView>;
54
- unschedule(orgId: string, postId: string, actor: Actor): Promise<PostView>;
49
+ create(input: CreatePostInput, actor: Actor): Promise<PostView>;
50
+ update(postId: string, patch: UpdatePostInput, actor: Actor): Promise<PostView>;
51
+ submit(postId: string, actor: Actor): Promise<PostView>;
52
+ schedule(postId: string, scheduledAt: Date, actor: Actor): Promise<PostView>;
53
+ unschedule(postId: string, actor: Actor): Promise<PostView>;
55
54
  publishScheduled(orgId: string, postId: string, now: Date): Promise<void>;
56
55
  private arm;
56
+ private syncCover;
57
57
  private disarm;
58
- approve(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
59
- remove(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
60
- restore(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
61
- pin(orgId: string, postId: string, actor: Actor): Promise<PostView>;
62
- unpin(orgId: string, postId: string, actor: Actor): Promise<PostView>;
63
- closeComments(orgId: string, postId: string, actor: Actor): Promise<PostView>;
64
- openComments(orgId: string, postId: string, actor: Actor): Promise<PostView>;
65
- setReactions(orgId: string, postId: string, actor: Actor, enabled: boolean): Promise<PostView>;
66
- setReaction(orgId: string, postId: string, actor: Actor, emoji: ReactionEmoji | null): Promise<PostReactions>;
67
- bookmark(orgId: string, postId: string, actor: Actor): Promise<void>;
68
- unbookmark(orgId: string, postId: string, actor: Actor): Promise<void>;
69
- flag(orgId: string, postId: string, actor: Actor, reason: string): Promise<PostFlag>;
70
- resolveFlags(orgId: string, postId: string, actor: Actor, resolution: Extract<PostFlagResolution, 'dismissed'>, communityId?: string): Promise<void>;
71
- spaceFeed(orgId: string, actor: Actor, communityId: string, spaceId: string, query: FeedQuery): Promise<Page<PostView>>;
72
- memberFeed(orgId: string, actor: Actor, communityId: string, query: Pick<FeedQuery, 'page' | 'pageSize'>): Promise<Page<PostView>>;
58
+ approve(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;
59
+ remove(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;
60
+ restore(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;
61
+ pin(postId: string, actor: Actor): Promise<PostView>;
62
+ unpin(postId: string, actor: Actor): Promise<PostView>;
63
+ closeComments(postId: string, actor: Actor): Promise<PostView>;
64
+ openComments(postId: string, actor: Actor): Promise<PostView>;
65
+ setReactions(postId: string, enabled: boolean, actor: Actor): Promise<PostView>;
66
+ setReaction(postId: string, emoji: ReactionEmoji | null, actor: Actor): Promise<PostReactions>;
67
+ bookmark(postId: string, actor: Actor): Promise<void>;
68
+ unbookmark(postId: string, actor: Actor): Promise<void>;
69
+ flag(postId: string, reason: string, actor: Actor): Promise<PostFlag>;
70
+ resolveFlags(postId: string, resolution: Extract<PostFlagResolution, 'dismissed'>, communityId: string | null, actor: Actor): Promise<void>;
71
+ spaceFeed(communityId: string, spaceId: string, query: FeedQuery, actor: Actor): Promise<Page<PostView>>;
72
+ memberFeed(communityId: string, query: Pick<FeedQuery, 'page' | 'pageSize'>, actor: Actor): Promise<Page<PostView>>;
73
73
  private openSpaceIdsIn;
74
- listOwn(orgId: string, actor: Actor, communityId: string, query: PageQuery): Promise<Page<PostView>>;
75
- listBookmarked(orgId: string, actor: Actor, communityId: string, query: PageQuery): Promise<Page<PostView>>;
76
- coverFile(orgId: string, actor: Actor, postId: string): Promise<string | null>;
77
- getPost(orgId: string, actor: Actor, postId: string): Promise<PostView>;
78
- moderationQueue(orgId: string, actor: Actor, communityId: string, spaceId: string): Promise<SpaceModerationQueue>;
79
- visible(orgId: string, actor: Actor, postId: string): Promise<{
74
+ listOwn(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;
75
+ listBookmarked(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;
76
+ coverFile(postId: string, actor: Actor): Promise<string>;
77
+ assetFile(postId: string, assetId: string, actor: Actor): Promise<string>;
78
+ private signShown;
79
+ getPost(postId: string, actor: Actor): Promise<PostView>;
80
+ moderationQueue(communityId: string, spaceId: string, actor: Actor): Promise<SpaceModerationQueue>;
81
+ private flaggedCommentViews;
82
+ visible(postId: string, actor: Actor): Promise<{
80
83
  post: Post;
81
84
  space: SpaceDetail;
82
85
  }>;
@@ -91,18 +94,21 @@ declare class PostServiceImpl implements PostService {
91
94
  private initial;
92
95
  private submissionOf;
93
96
  private requireOwnedImage;
97
+ private requireBodyAssets;
94
98
  private openSpace;
95
99
  private requireOpen;
96
100
  private loadVisible;
97
101
  private canSee;
98
102
  private visibleStatuses;
99
- private canModerate;
103
+ private moderates;
104
+ private requireModerator;
100
105
  private assertAuthor;
101
106
  private topicFits;
102
107
  private assertTopic;
103
108
  private patchChanges;
104
109
  private mentionable;
105
110
  private openersOf;
111
+ private mentionedOpeners;
106
112
  private landed;
107
113
  private published;
108
114
  private updated;