@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.
- package/dist/assets/events.d.ts +9 -9
- package/dist/assets/index.d.ts +3 -2
- package/dist/assets/index.js +2 -1
- package/dist/assets/jobs.d.ts +15 -0
- package/dist/assets/jobs.js +30 -0
- package/dist/assets/jobs.js.map +1 -0
- package/dist/assets/ports.d.ts +4 -3
- package/dist/assets/service.d.ts +0 -1
- package/dist/assets/service.js +0 -4
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js +2 -63
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.d.ts +1 -2
- package/dist/automations/catalog.js +69 -62
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +4 -4
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +3 -17
- package/dist/automations/community-mail.js +1 -21
- package/dist/automations/community-mail.js.map +1 -1
- package/dist/automations/events.d.ts +0 -10
- package/dist/automations/index.d.ts +11 -4
- package/dist/automations/index.js +8 -1
- package/dist/automations/notifications/debounce.d.ts +25 -0
- package/dist/automations/notifications/debounce.js +93 -0
- package/dist/automations/notifications/debounce.js.map +1 -0
- package/dist/automations/notifications/defaults.d.ts +8 -0
- package/dist/automations/notifications/defaults.js +316 -0
- package/dist/automations/notifications/defaults.js.map +1 -0
- package/dist/automations/notifications/delivery.d.ts +28 -0
- package/dist/automations/notifications/delivery.js +88 -0
- package/dist/automations/notifications/delivery.js.map +1 -0
- package/dist/automations/notifications/processor.d.ts +64 -0
- package/dist/automations/notifications/processor.js +270 -0
- package/dist/automations/notifications/processor.js.map +1 -0
- package/dist/automations/notifications/rules/content.d.ts +6 -0
- package/dist/automations/notifications/rules/content.js +39 -0
- package/dist/automations/notifications/rules/content.js.map +1 -0
- package/dist/automations/notifications/rules/discussion.d.ts +7 -0
- package/dist/automations/notifications/rules/discussion.js +678 -0
- package/dist/automations/notifications/rules/discussion.js.map +1 -0
- package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
- package/dist/automations/notifications/rules/entitlements.js +249 -0
- package/dist/automations/notifications/rules/entitlements.js.map +1 -0
- package/dist/automations/notifications/rules/index.d.ts +6 -0
- package/dist/automations/notifications/rules/index.js +15 -0
- package/dist/automations/notifications/rules/index.js.map +1 -0
- package/dist/automations/notifications/rules/organizations.d.ts +6 -0
- package/dist/automations/notifications/rules/organizations.js +28 -0
- package/dist/automations/notifications/rules/organizations.js.map +1 -0
- package/dist/automations/notifications/rules/recipients.d.ts +32 -0
- package/dist/automations/notifications/rules/recipients.js +99 -0
- package/dist/automations/notifications/rules/recipients.js.map +1 -0
- package/dist/automations/notifications/service.d.ts +27 -0
- package/dist/automations/notifications/service.js +45 -0
- package/dist/automations/notifications/service.js.map +1 -0
- package/dist/automations/notifications/types.d.ts +7 -0
- package/dist/automations/notifications/types.js +473 -0
- package/dist/automations/notifications/types.js.map +1 -0
- package/dist/automations/ports.d.ts +119 -5
- package/dist/automations/service.d.ts +10 -13
- package/dist/automations/service.js +21 -83
- package/dist/automations/service.js.map +1 -1
- package/dist/content/events.d.ts +130 -80
- package/dist/content/events.js +11 -0
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +6 -4
- package/dist/content/index.js +3 -1
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +36 -0
- package/dist/content/notification-categories.js +64 -0
- package/dist/content/notification-categories.js.map +1 -0
- package/dist/content/ports.d.ts +25 -6
- package/dist/content/service.d.ts +4 -4
- package/dist/content/service.js +24 -8
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +22 -0
- package/dist/content/titles.js +69 -0
- package/dist/content/titles.js.map +1 -0
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/chats.d.ts +9 -4
- package/dist/discussion/chats.js +80 -47
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/digests.d.ts +24 -0
- package/dist/discussion/digests.js +104 -0
- package/dist/discussion/digests.js.map +1 -0
- package/dist/discussion/events.d.ts +194 -28
- package/dist/discussion/events.js +74 -17
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +7 -2
- package/dist/discussion/index.js +6 -1
- package/dist/discussion/jobs.d.ts +16 -0
- package/dist/discussion/jobs.js +40 -0
- package/dist/discussion/jobs.js.map +1 -0
- package/dist/discussion/mentions.d.ts +11 -0
- package/dist/discussion/mentions.js +35 -0
- package/dist/discussion/mentions.js.map +1 -0
- package/dist/discussion/ports.d.ts +107 -24
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +18 -8
- package/dist/discussion/posts.js +207 -155
- package/dist/discussion/posts.js.map +1 -1
- package/dist/discussion/service.d.ts +17 -8
- package/dist/discussion/service.js +105 -41
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +37 -0
- package/dist/discussion/subscriptions.js +99 -0
- package/dist/discussion/subscriptions.js.map +1 -0
- package/dist/discussion/titles.d.ts +26 -0
- package/dist/discussion/titles.js +84 -0
- package/dist/discussion/titles.js.map +1 -0
- package/dist/entitlements/events.d.ts +20 -20
- package/dist/entitlements/index.d.ts +2 -1
- package/dist/entitlements/index.js +2 -1
- package/dist/entitlements/jobs.d.ts +12 -0
- package/dist/entitlements/jobs.js +18 -0
- package/dist/entitlements/jobs.js.map +1 -0
- package/dist/entitlements/ports.d.ts +7 -3
- package/dist/entitlements/service.d.ts +5 -2
- package/dist/entitlements/service.js +23 -3
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +30 -0
- package/dist/identity/devices.js +107 -0
- package/dist/identity/devices.js.map +1 -0
- package/dist/identity/events.d.ts +2 -2
- package/dist/identity/index.d.ts +5 -4
- package/dist/identity/index.js +3 -2
- package/dist/identity/model.d.ts +2 -2
- package/dist/identity/notification-settings.d.ts +28 -0
- package/dist/identity/notification-settings.js +113 -0
- package/dist/identity/notification-settings.js.map +1 -0
- package/dist/identity/ports.d.ts +78 -54
- package/dist/integrations/events.d.ts +3 -3
- package/dist/organizations/events.d.ts +32 -32
- package/dist/organizations/index.d.ts +2 -1
- package/dist/organizations/index.js +2 -1
- package/dist/organizations/notification-categories.d.ts +32 -0
- package/dist/organizations/notification-categories.js +51 -0
- package/dist/organizations/notification-categories.js.map +1 -0
- package/dist/organizations/ports.d.ts +3 -0
- package/dist/organizations/service.d.ts +2 -0
- package/dist/organizations/service.js +7 -0
- package/dist/organizations/service.js.map +1 -1
- package/dist/progress/events.d.ts +14 -14
- package/dist/reporting/member/index.d.ts +2 -2
- package/dist/reporting/member/model.d.ts +56 -23
- package/dist/reporting/member/ports.d.ts +9 -6
- package/dist/reporting/member/service.d.ts +33 -14
- package/dist/reporting/member/service.js +138 -60
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/errors.d.ts +13 -1
- package/dist/shared/errors.js +18 -1
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +3 -0
- package/dist/shared/id.js +4 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/mailer.d.ts +11 -1
- package/dist/shared/mailer.js +32 -5
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/notification-categories.d.ts +16 -0
- package/dist/shared/notification-categories.js +94 -0
- package/dist/shared/notification-categories.js.map +1 -0
- package/dist/shared/notification-settings.d.ts +14 -0
- package/dist/shared/notification-settings.js +61 -0
- package/dist/shared/notification-settings.js.map +1 -0
- package/dist/shared/ports.d.ts +48 -2
- package/dist/shared/settings.d.ts +2 -1
- package/dist/shared/settings.js +1 -0
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/automations.d.ts +8 -2
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +2 -2
- package/dist/types/email-templates.d.ts +97 -3
- package/dist/types/identity.d.ts +2 -2
- package/dist/types/index.d.ts +7 -6
- package/dist/types/notifications.d.ts +157 -0
- package/dist/types/notifications.js +1 -0
- package/dist/types/schemas/automations.d.ts +1 -10
- package/dist/types/schemas/automations.js +1 -3
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +62 -1
- package/dist/types/schemas/content.js +25 -3
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +85 -12
- package/dist/types/schemas/discussion.js +47 -13
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +1 -85
- package/dist/types/schemas/identity.js +1 -42
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +6 -5
- package/dist/types/schemas/index.js +6 -5
- package/dist/types/schemas/notifications.d.ts +95 -0
- package/dist/types/schemas/notifications.js +56 -0
- package/dist/types/schemas/notifications.js.map +1 -0
- package/dist/types/schemas/progress.d.ts +3 -3
- package/package.json +1 -1
- package/dist/automations/community-notifications.d.ts +0 -16
- package/dist/automations/community-notifications.js +0 -84
- package/dist/automations/community-notifications.js.map +0 -1
- package/dist/automations/seeds.d.ts +0 -17
- package/dist/automations/seeds.js +0 -235
- package/dist/automations/seeds.js.map +0 -1
- package/dist/identity/notifications.d.ts +0 -27
- package/dist/identity/notifications.js +0 -101
- package/dist/identity/notifications.js.map +0 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { idSchema } from "../types/schemas/shared.js";
|
|
2
|
+
import { noopLogger } from "../shared/logger.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/discussion/jobs.ts
|
|
5
|
+
const POST_PUBLISH_JOB = "discussion.post.publish";
|
|
6
|
+
const DIGEST_OPEN_JOB = "discussion.digest.open";
|
|
7
|
+
const postPublishPayload = z.object({
|
|
8
|
+
orgId: idSchema,
|
|
9
|
+
postId: idSchema
|
|
10
|
+
}).strict();
|
|
11
|
+
function discussionJobs(deps) {
|
|
12
|
+
const logger = deps.logger ?? noopLogger;
|
|
13
|
+
return [{
|
|
14
|
+
job: POST_PUBLISH_JOB,
|
|
15
|
+
async run(payload, fire) {
|
|
16
|
+
const parsed = postPublishPayload.safeParse(payload);
|
|
17
|
+
if (!parsed.success) {
|
|
18
|
+
logger.error("scheduled post payload refused", {
|
|
19
|
+
job: POST_PUBLISH_JOB,
|
|
20
|
+
fireId: fire.id
|
|
21
|
+
});
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
await deps.posts.publishScheduled(parsed.data.orgId, parsed.data.postId, fire.startedAt);
|
|
25
|
+
}
|
|
26
|
+
}, {
|
|
27
|
+
job: DIGEST_OPEN_JOB,
|
|
28
|
+
async run(_payload, fire) {
|
|
29
|
+
await deps.digests.openDue(fire.startedAt);
|
|
30
|
+
}
|
|
31
|
+
}];
|
|
32
|
+
}
|
|
33
|
+
const discussionRecurringJobs = [{
|
|
34
|
+
job: DIGEST_OPEN_JOB,
|
|
35
|
+
cron: "0 * * * *"
|
|
36
|
+
}];
|
|
37
|
+
//#endregion
|
|
38
|
+
export { DIGEST_OPEN_JOB, POST_PUBLISH_JOB, discussionJobs, discussionRecurringJobs };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","names":[],"sources":["../../src/discussion/jobs.ts"],"sourcesContent":["import { z } from 'zod';\nimport { idSchema } from '../types/schemas/index.js';\nimport type { Logger, RecurringJob, ScheduledJob } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type { DigestService, PostService } from './ports.js';\n\nexport const POST_PUBLISH_JOB = 'discussion.post.publish';\nexport const DIGEST_OPEN_JOB = 'discussion.digest.open';\n\nconst postPublishPayload = z.object({ orgId: idSchema, postId: idSchema }).strict();\n\nexport interface DiscussionJobDeps {\n posts: Pick<PostService, 'publishScheduled'>;\n digests: Pick<DigestService, 'openDue'>;\n logger?: Logger;\n}\n\nexport function discussionJobs(deps: DiscussionJobDeps): ScheduledJob[] {\n const logger = deps.logger ?? noopLogger;\n return [\n {\n job: POST_PUBLISH_JOB,\n async run(payload, fire) {\n const parsed = postPublishPayload.safeParse(payload);\n if (!parsed.success) {\n logger.error('scheduled post payload refused', {\n job: POST_PUBLISH_JOB,\n fireId: fire.id,\n });\n return;\n }\n await deps.posts.publishScheduled(parsed.data.orgId, parsed.data.postId, fire.startedAt);\n },\n },\n {\n job: DIGEST_OPEN_JOB,\n async run(_payload, fire) {\n await deps.digests.openDue(fire.startedAt);\n },\n },\n ];\n}\n\nexport const discussionRecurringJobs: RecurringJob[] = [\n { job: DIGEST_OPEN_JOB, cron: '0 * * * *' },\n];\n"],"mappings":";;;;AAMA,MAAa,mBAAmB;AAChC,MAAa,kBAAkB;AAE/B,MAAM,qBAAqB,EAAE,OAAO;CAAE,OAAO;CAAU,QAAQ;AAAS,CAAC,CAAC,CAAC,OAAO;AAQlF,SAAgB,eAAe,MAAyC;CACtE,MAAM,SAAS,KAAK,UAAU;CAC9B,OAAO,CACL;EACE,KAAK;EACL,MAAM,IAAI,SAAS,MAAM;GACvB,MAAM,SAAS,mBAAmB,UAAU,OAAO;GACnD,IAAI,CAAC,OAAO,SAAS;IACnB,OAAO,MAAM,kCAAkC;KAC7C,KAAK;KACL,QAAQ,KAAK;IACf,CAAC;IACD;GACF;GACA,MAAM,KAAK,MAAM,iBAAiB,OAAO,KAAK,OAAO,OAAO,KAAK,QAAQ,KAAK,SAAS;EACzF;CACF,GACA;EACE,KAAK;EACL,MAAM,IAAI,UAAU,MAAM;GACxB,MAAM,KAAK,QAAQ,QAAQ,KAAK,SAAS;EAC3C;CACF,CACF;AACF;AAEA,MAAa,0BAA0C,CACrD;CAAE,KAAK;CAAiB,MAAM;AAAY,CAC5C"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MentionItemType, MentionView } from "../types/schemas/discussion.js";
|
|
2
|
+
import "../types/index.js";
|
|
3
|
+
import { AuthorProfileReader, MentionsRepository } from "./ports.js";
|
|
4
|
+
//#region src/discussion/mentions.d.ts
|
|
5
|
+
declare function mentionTokens(...texts: string[]): string[];
|
|
6
|
+
declare function nameMentions(text: string, nameOf: (orgUserId: string) => Promise<string>): Promise<string>;
|
|
7
|
+
declare function keepAllowed(tokens: string[], allowed: string[]): string[];
|
|
8
|
+
declare function mentionViews(mentions: Pick<MentionsRepository, 'forItems'>, profiles: AuthorProfileReader, orgId: string, itemType: MentionItemType, itemIds: string[]): Promise<Record<string, MentionView[]>>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { keepAllowed, mentionTokens, mentionViews, nameMentions };
|
|
11
|
+
//# sourceMappingURL=mentions.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { authorView } from "./authors.js";
|
|
2
|
+
//#region src/discussion/mentions.ts
|
|
3
|
+
const TOKEN = /<@([A-Za-z0-9_-]+)>/g;
|
|
4
|
+
const UNNAMED = "A member";
|
|
5
|
+
function mentionTokens(...texts) {
|
|
6
|
+
const found = /* @__PURE__ */ new Set();
|
|
7
|
+
for (const text of texts) for (const match of text.matchAll(TOKEN)) found.add(match[1]);
|
|
8
|
+
return [...found];
|
|
9
|
+
}
|
|
10
|
+
async function nameMentions(text, nameOf) {
|
|
11
|
+
const names = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const orgUserId of mentionTokens(text)) names.set(orgUserId, await nameOf(orgUserId));
|
|
13
|
+
return text.replace(TOKEN, (_token, orgUserId) => names.get(orgUserId) ?? UNNAMED);
|
|
14
|
+
}
|
|
15
|
+
function keepAllowed(tokens, allowed) {
|
|
16
|
+
const kept = new Set(allowed);
|
|
17
|
+
return tokens.filter((token) => kept.has(token));
|
|
18
|
+
}
|
|
19
|
+
async function mentionViews(mentions, profiles, orgId, itemType, itemIds) {
|
|
20
|
+
if (itemIds.length === 0) return {};
|
|
21
|
+
const byItem = await mentions.forItems(orgId, itemType, [...new Set(itemIds)]);
|
|
22
|
+
const people = [...new Set(Object.values(byItem).flat())];
|
|
23
|
+
const records = people.length > 0 ? await profiles.authorsOf(orgId, people) : {};
|
|
24
|
+
return Object.fromEntries(Object.entries(byItem).map(([itemId, orgUserIds]) => [itemId, orgUserIds.flatMap((orgUserId) => {
|
|
25
|
+
const record = records[orgUserId];
|
|
26
|
+
return record ? [{
|
|
27
|
+
orgUserId,
|
|
28
|
+
name: authorView(orgId, record, null).displayName || UNNAMED
|
|
29
|
+
}] : [];
|
|
30
|
+
})]));
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { keepAllowed, mentionTokens, mentionViews, nameMentions };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=mentions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mentions.js","names":[],"sources":["../../src/discussion/mentions.ts"],"sourcesContent":["import type { MentionItemType, MentionView } from '../types/index.js';\nimport { authorView } from './authors.js';\nimport type { AuthorProfileReader, MentionsRepository } from './ports.js';\n\nconst TOKEN = /<@([A-Za-z0-9_-]+)>/g;\nconst UNNAMED = 'A member';\n\nexport function mentionTokens(...texts: string[]): string[] {\n const found = new Set<string>();\n for (const text of texts) {\n for (const match of text.matchAll(TOKEN)) {\n found.add(match[1]!);\n }\n }\n return [...found];\n}\n\nexport async function nameMentions(\n text: string,\n nameOf: (orgUserId: string) => Promise<string>,\n): Promise<string> {\n const names = new Map<string, string>();\n for (const orgUserId of mentionTokens(text)) {\n names.set(orgUserId, await nameOf(orgUserId));\n }\n return text.replace(TOKEN, (_token, orgUserId: string) => names.get(orgUserId) ?? UNNAMED);\n}\n\nexport function keepAllowed(tokens: string[], allowed: string[]): string[] {\n const kept = new Set(allowed);\n return tokens.filter((token) => kept.has(token));\n}\n\nexport async function mentionViews(\n mentions: Pick<MentionsRepository, 'forItems'>,\n profiles: AuthorProfileReader,\n orgId: string,\n itemType: MentionItemType,\n itemIds: string[],\n): Promise<Record<string, MentionView[]>> {\n if (itemIds.length === 0) {\n return {};\n }\n const byItem = await mentions.forItems(orgId, itemType, [...new Set(itemIds)]);\n const people = [...new Set(Object.values(byItem).flat())];\n const records = people.length > 0 ? await profiles.authorsOf(orgId, people) : {};\n return Object.fromEntries(\n Object.entries(byItem).map(([itemId, orgUserIds]) => [\n itemId,\n orgUserIds.flatMap((orgUserId) => {\n const record = records[orgUserId];\n return record\n ? [{ orgUserId, name: authorView(orgId, record, null).displayName || UNNAMED }]\n : [];\n }),\n ]),\n );\n}\n"],"mappings":";;AAIA,MAAM,QAAQ;AACd,MAAM,UAAU;AAEhB,SAAgB,cAAc,GAAG,OAA2B;CAC1D,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,QAAQ,OACjB,KAAK,MAAM,SAAS,KAAK,SAAS,KAAK,GACrC,MAAM,IAAI,MAAM,EAAG;CAGvB,OAAO,CAAC,GAAG,KAAK;AAClB;AAEA,eAAsB,aACpB,MACA,QACiB;CACjB,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,aAAa,cAAc,IAAI,GACxC,MAAM,IAAI,WAAW,MAAM,OAAO,SAAS,CAAC;CAE9C,OAAO,KAAK,QAAQ,QAAQ,QAAQ,cAAsB,MAAM,IAAI,SAAS,KAAK,OAAO;AAC3F;AAEA,SAAgB,YAAY,QAAkB,SAA6B;CACzE,MAAM,OAAO,IAAI,IAAI,OAAO;CAC5B,OAAO,OAAO,QAAQ,UAAU,KAAK,IAAI,KAAK,CAAC;AACjD;AAEA,eAAsB,aACpB,UACA,UACA,OACA,UACA,SACwC;CACxC,IAAI,QAAQ,WAAW,GACrB,OAAO,CAAC;CAEV,MAAM,SAAS,MAAM,SAAS,SAAS,OAAO,UAAU,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,CAAC;CAC7E,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;CACxD,MAAM,UAAU,OAAO,SAAS,IAAI,MAAM,SAAS,UAAU,OAAO,MAAM,IAAI,CAAC;CAC/E,OAAO,OAAO,YACZ,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,QAAQ,gBAAgB,CACnD,QACA,WAAW,SAAS,cAAc;EAChC,MAAM,SAAS,QAAQ;EACvB,OAAO,SACH,CAAC;GAAE;GAAW,MAAM,WAAW,OAAO,QAAQ,IAAI,CAAC,CAAC,eAAe;EAAQ,CAAC,IAC5E,CAAC;CACP,CAAC,CACH,CAAC,CACH;AACF"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { JsonValue, Page } from "../types/shared.js";
|
|
2
2
|
import { Activity, SpaceDetail } from "../types/schemas/content.js";
|
|
3
3
|
import { Role } from "../types/schemas/organizations.js";
|
|
4
|
-
import { Chat, ChatLastMessage, ChatUser, CommentListItem, CommentView, CommentsConfig, FlaggedComment, ListCommentsQuery, Message, MessageFlag, MessageFlagResolution, MessageView, Post, PostFlag, PostFlagResolution, PostStatus, PostView, ReactionCounts, ReactionEmoji } from "../types/schemas/discussion.js";
|
|
4
|
+
import { Chat, ChatLastMessage, ChatUser, CommentListItem, CommentView, CommentsConfig, DigestRun, FlaggedComment, ListCommentsQuery, MentionItemType, Message, MessageFlag, MessageFlagResolution, MessageView, Post, PostFlag, PostFlagResolution, PostStatus, PostView, ReactionCounts, ReactionEmoji, Subscription, SubscriptionItemType, SubscriptionView } from "../types/schemas/discussion.js";
|
|
5
5
|
import { Comment, CommentReport } from "../types/discussion.js";
|
|
6
|
+
import { SubjectRef } from "../types/notifications.js";
|
|
6
7
|
import "../types/index.js";
|
|
7
8
|
import { OutboxAppender, UnitOfWork } from "../shared/ports.js";
|
|
8
9
|
import { Actor } from "../shared/actor.js";
|
|
10
|
+
import { CommunityDigest } from "../content/ports.js";
|
|
9
11
|
import "../content/index.js";
|
|
10
12
|
import "../organizations/index.js";
|
|
11
13
|
import "./model.js";
|
|
@@ -51,6 +53,13 @@ interface AuthorRecord {
|
|
|
51
53
|
imageAssetOrgId: string | null;
|
|
52
54
|
imageAssetId: string | null;
|
|
53
55
|
}
|
|
56
|
+
interface ItemTitle {
|
|
57
|
+
title: string;
|
|
58
|
+
excerpt?: string;
|
|
59
|
+
}
|
|
60
|
+
interface ItemTitles {
|
|
61
|
+
titlesFor(orgId: string, actor: Actor, refs: SubjectRef[]): Promise<Record<string, ItemTitle>>;
|
|
62
|
+
}
|
|
54
63
|
interface AuthorProfileReader {
|
|
55
64
|
authorsOf(orgId: string, orgUserIds: string[]): Promise<Record<string, AuthorRecord>>;
|
|
56
65
|
}
|
|
@@ -88,7 +97,11 @@ interface DiscussionService {
|
|
|
88
97
|
reportComment(orgId: string, commentId: string, actor: Actor, reason: string): Promise<CommentReport>;
|
|
89
98
|
/** Plain read of one comment — no rules applied. */
|
|
90
99
|
getComment(orgId: string, commentId: string): Promise<Comment | null>;
|
|
91
|
-
|
|
100
|
+
commenterIds(orgId: string, target: {
|
|
101
|
+
postId: string;
|
|
102
|
+
} | {
|
|
103
|
+
activityId: string;
|
|
104
|
+
}): Promise<string[]>;
|
|
92
105
|
}
|
|
93
106
|
interface ContentAccessReader {
|
|
94
107
|
hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean>;
|
|
@@ -99,13 +112,18 @@ interface ActivityGetter {
|
|
|
99
112
|
interface DiscussionRepository extends PostCommentReader, AuthorProfileReader {
|
|
100
113
|
insertComment(orgId: string, comment: Comment): Promise<Comment>;
|
|
101
114
|
findComment(orgId: string, id: string): Promise<Comment | null>;
|
|
115
|
+
commenterIds(orgId: string, target: {
|
|
116
|
+
postId: string;
|
|
117
|
+
} | {
|
|
118
|
+
activityId: string;
|
|
119
|
+
}): Promise<string[]>;
|
|
102
120
|
updateComment(orgId: string, id: string, patch: Partial<Pick<Comment, 'body' | 'status' | 'removedBy' | 'updatedAt'>>): Promise<Comment | null>;
|
|
103
121
|
/** Every comment on the activity, oldest first, including removed ones. */
|
|
104
122
|
listByActivity(orgId: string, activityId: string): Promise<Comment[]>;
|
|
105
123
|
/** A page of the post's top-level threads the viewer can see, oldest first, with their replies. `total` counts threads. */
|
|
106
124
|
listThreadsByPost(orgId: string, postId: string, query: PostThreadsQuery): Promise<Page<Comment>>;
|
|
107
125
|
reactionsOf(orgId: string, commentIds: string[], viewerId: string): Promise<Record<string, CommentReactions>>;
|
|
108
|
-
setReaction(orgId: string, commentId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<
|
|
126
|
+
setReaction(orgId: string, commentId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<boolean>;
|
|
109
127
|
/** Returns null when this person has already reported this comment. */
|
|
110
128
|
insertReport(orgId: string, report: CommentReport): Promise<CommentReport | null>;
|
|
111
129
|
/** Every unresolved report against the given comments. */
|
|
@@ -121,6 +139,7 @@ interface DiscussionRepository extends PostCommentReader, AuthorProfileReader {
|
|
|
121
139
|
* entry commit in one transaction. */
|
|
122
140
|
interface DiscussionWriteScope {
|
|
123
141
|
discussion: DiscussionRepository;
|
|
142
|
+
mentions: MentionsRepository;
|
|
124
143
|
outbox: OutboxAppender;
|
|
125
144
|
}
|
|
126
145
|
type DiscussionUnitOfWork = UnitOfWork<DiscussionWriteScope>;
|
|
@@ -175,12 +194,22 @@ interface PostCommentReader {
|
|
|
175
194
|
listOpenFlagsForSpace(orgId: string, spaceId: string, limit: number): Promise<FlaggedComment[]>;
|
|
176
195
|
publishedCommentCounts(orgId: string, postIds: string[]): Promise<Record<string, number>>;
|
|
177
196
|
}
|
|
197
|
+
interface PublishedPost {
|
|
198
|
+
postId: string;
|
|
199
|
+
spaceId: string;
|
|
200
|
+
title: string;
|
|
201
|
+
orgUserId: string;
|
|
202
|
+
commentCount: number;
|
|
203
|
+
}
|
|
178
204
|
interface PostService {
|
|
179
205
|
visibleTitles(orgId: string, actor: Actor, postIds: string[]): Promise<Record<string, string>>;
|
|
206
|
+
publishedBetween(orgId: string, communityId: string, from: Date, to: Date): Promise<PublishedPost[]>;
|
|
180
207
|
create(orgId: string, actor: Actor, input: CreatePostInput): Promise<PostView>;
|
|
181
208
|
update(orgId: string, postId: string, actor: Actor, patch: UpdatePostInput): Promise<PostView>;
|
|
182
209
|
submit(orgId: string, postId: string, actor: Actor): Promise<PostView>;
|
|
183
210
|
schedule(orgId: string, postId: string, actor: Actor, scheduledAt: Date): Promise<PostView>;
|
|
211
|
+
unschedule(orgId: string, postId: string, actor: Actor): Promise<PostView>;
|
|
212
|
+
publishScheduled(orgId: string, postId: string, now: Date): Promise<void>;
|
|
184
213
|
approve(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
|
|
185
214
|
remove(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
|
|
186
215
|
restore(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
|
|
@@ -190,8 +219,6 @@ interface PostService {
|
|
|
190
219
|
openComments(orgId: string, postId: string, actor: Actor): Promise<PostView>;
|
|
191
220
|
setReactions(orgId: string, postId: string, actor: Actor, enabled: boolean): Promise<PostView>;
|
|
192
221
|
setReaction(orgId: string, postId: string, actor: Actor, emoji: ReactionEmoji | null): Promise<PostReactions>;
|
|
193
|
-
follow(orgId: string, postId: string, actor: Actor): Promise<void>;
|
|
194
|
-
unfollow(orgId: string, postId: string, actor: Actor): Promise<void>;
|
|
195
222
|
bookmark(orgId: string, postId: string, actor: Actor): Promise<void>;
|
|
196
223
|
unbookmark(orgId: string, postId: string, actor: Actor): Promise<void>;
|
|
197
224
|
flag(orgId: string, postId: string, actor: Actor, reason: string): Promise<PostFlag>;
|
|
@@ -208,29 +235,26 @@ interface PostService {
|
|
|
208
235
|
space: SpaceDetail;
|
|
209
236
|
}>;
|
|
210
237
|
find(orgId: string, postId: string): Promise<Post | null>;
|
|
211
|
-
followerIds(orgId: string, postId: string): Promise<string[]>;
|
|
212
238
|
moderationQueue(orgId: string, actor: Actor, communityId: string, spaceId: string): Promise<SpaceModerationQueue>;
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
}
|
|
240
|
+
interface PostGuard {
|
|
241
|
+
status: PostStatus;
|
|
242
|
+
scheduledAt?: Date;
|
|
243
|
+
dueBy?: Date;
|
|
215
244
|
}
|
|
216
245
|
interface PostRepository {
|
|
217
246
|
insert(orgId: string, post: Post): Promise<Post>;
|
|
218
247
|
update(orgId: string, id: string, patch: PostPatch): Promise<Post | null>;
|
|
248
|
+
updateIf(orgId: string, id: string, guard: PostGuard, patch: PostPatch): Promise<Post | null>;
|
|
219
249
|
find(orgId: string, id: string): Promise<Post | null>;
|
|
220
250
|
findMany(orgId: string, ids: string[]): Promise<Post[]>;
|
|
251
|
+
publishedBetween(orgId: string, communityId: string, from: Date, to: Date): Promise<PublishedPost[]>;
|
|
221
252
|
listPublished(orgId: string, spaceIds: string[], query: FeedQuery): Promise<Page<Post>>;
|
|
222
253
|
listByAuthor(orgId: string, orgUserId: string, spaceIds: string[], query: PageQuery): Promise<Page<Post>>;
|
|
223
254
|
listBookmarked(orgId: string, orgUserId: string, spaceIds: string[], query: BookmarkQuery): Promise<Page<Post>>;
|
|
224
255
|
listPending(orgId: string, spaceId: string, limit: number): Promise<Post[]>;
|
|
225
|
-
/** Due scheduled posts in id order, after `afterId` when given. */
|
|
226
|
-
listDue(orgId: string, now: Date, limit: number, afterId?: string): Promise<Post[]>;
|
|
227
|
-
hasDue(orgId: string, now: Date): Promise<boolean>;
|
|
228
256
|
reactionsOf(orgId: string, postIds: string[], viewerId: string): Promise<Record<string, PostReactions>>;
|
|
229
|
-
setReaction(orgId: string, postId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<
|
|
230
|
-
follow(orgId: string, postId: string, orgUserId: string): Promise<void>;
|
|
231
|
-
unfollow(orgId: string, postId: string, orgUserId: string): Promise<void>;
|
|
232
|
-
followerIds(orgId: string, postId: string): Promise<string[]>;
|
|
233
|
-
followedAmong(orgId: string, postIds: string[], orgUserId: string): Promise<string[]>;
|
|
257
|
+
setReaction(orgId: string, postId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<boolean>;
|
|
234
258
|
bookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;
|
|
235
259
|
unbookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;
|
|
236
260
|
bookmarkedAmong(orgId: string, postIds: string[], orgUserId: string): Promise<string[]>;
|
|
@@ -245,9 +269,25 @@ interface PostRepository {
|
|
|
245
269
|
}
|
|
246
270
|
interface PostWriteScope {
|
|
247
271
|
posts: PostRepository;
|
|
272
|
+
mentions: MentionsRepository;
|
|
248
273
|
outbox: OutboxAppender;
|
|
249
274
|
}
|
|
250
275
|
type PostUnitOfWork = UnitOfWork<PostWriteScope>;
|
|
276
|
+
interface DigestScheduleReader {
|
|
277
|
+
communitiesWithDigest(): Promise<CommunityDigest[]>;
|
|
278
|
+
}
|
|
279
|
+
interface DigestRunRepository {
|
|
280
|
+
listRuns(): Promise<DigestRun[]>;
|
|
281
|
+
advance(orgId: string, communityId: string, dueAt: Date): Promise<boolean>;
|
|
282
|
+
}
|
|
283
|
+
interface DigestWriteScope {
|
|
284
|
+
digests: DigestRunRepository;
|
|
285
|
+
outbox: OutboxAppender;
|
|
286
|
+
}
|
|
287
|
+
type DigestUnitOfWork = UnitOfWork<DigestWriteScope>;
|
|
288
|
+
interface DigestService {
|
|
289
|
+
openDue(now: Date): Promise<void>;
|
|
290
|
+
}
|
|
251
291
|
interface StartDirectInput {
|
|
252
292
|
communityId: string;
|
|
253
293
|
orgUserId: string;
|
|
@@ -267,11 +307,19 @@ interface ChatListItem {
|
|
|
267
307
|
chat: Chat;
|
|
268
308
|
participantIds: string[];
|
|
269
309
|
lastReadMessageId: string | null;
|
|
270
|
-
|
|
271
|
-
mutedUntil: Date | null;
|
|
310
|
+
subscription: SubscriptionView | null;
|
|
272
311
|
unread: number;
|
|
273
312
|
lastMessage: ChatLastMessage | null;
|
|
274
313
|
}
|
|
314
|
+
type ChatInboxRow = Omit<ChatListItem, 'subscription'>;
|
|
315
|
+
interface ChatUnread {
|
|
316
|
+
count: number;
|
|
317
|
+
latest: {
|
|
318
|
+
messageId: string;
|
|
319
|
+
senderOrgUserId: string;
|
|
320
|
+
body: string;
|
|
321
|
+
} | null;
|
|
322
|
+
}
|
|
275
323
|
interface MessageReactions {
|
|
276
324
|
reactions: ReactionCounts;
|
|
277
325
|
viewerReaction?: ReactionEmoji;
|
|
@@ -300,8 +348,6 @@ interface ChatService {
|
|
|
300
348
|
deleteMessage(orgId: string, actor: Actor, messageId: string): Promise<MessageView>;
|
|
301
349
|
setReaction(orgId: string, actor: Actor, messageId: string, emoji: ReactionEmoji | null): Promise<MessageReactions>;
|
|
302
350
|
markRead(orgId: string, actor: Actor, chatId: string, messageId: string): Promise<void>;
|
|
303
|
-
mute(orgId: string, actor: Actor, chatId: string, until?: Date): Promise<void>;
|
|
304
|
-
unmute(orgId: string, actor: Actor, chatId: string): Promise<void>;
|
|
305
351
|
block(orgId: string, actor: Actor, communityId: string, orgUserId: string): Promise<void>;
|
|
306
352
|
unblock(orgId: string, actor: Actor, communityId: string, orgUserId: string): Promise<void>;
|
|
307
353
|
flagMessage(orgId: string, actor: Actor, messageId: string, reason: string): Promise<MessageFlag>;
|
|
@@ -314,6 +360,8 @@ interface ChatService {
|
|
|
314
360
|
participants: ChatUser[];
|
|
315
361
|
} | null>;
|
|
316
362
|
latestMessageBody(orgId: string, chatId: string): Promise<string | null>;
|
|
363
|
+
unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;
|
|
364
|
+
findMessage(orgId: string, messageId: string): Promise<Message | null>;
|
|
317
365
|
isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;
|
|
318
366
|
titlesFor(orgId: string, actor: Actor, chatIds: string[]): Promise<Record<string, string>>;
|
|
319
367
|
messages(orgId: string, actor: Actor, chatId: string, query: Pick<FeedQuery, 'page' | 'pageSize'>): Promise<Page<MessageView>>;
|
|
@@ -328,12 +376,12 @@ interface ChatRepository {
|
|
|
328
376
|
insertChat(orgId: string, chat: Chat): Promise<Chat>;
|
|
329
377
|
updateChat(orgId: string, id: string, patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>): Promise<Chat | null>;
|
|
330
378
|
findChat(orgId: string, id: string): Promise<Chat | null>;
|
|
331
|
-
listForUser(orgId: string, communityId: string, orgUserId: string, query: PageQuery): Promise<Page<
|
|
379
|
+
listForUser(orgId: string, communityId: string, orgUserId: string, query: PageQuery): Promise<Page<ChatInboxRow>>;
|
|
332
380
|
participants(orgId: string, chatId: string): Promise<ChatUser[]>;
|
|
333
381
|
join(orgId: string, user: ChatUser): Promise<void>;
|
|
334
382
|
markLeft(orgId: string, chatId: string, orgUserId: string, leftAt: Date): Promise<void>;
|
|
335
383
|
setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<void>;
|
|
336
|
-
|
|
384
|
+
unreadFor(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;
|
|
337
385
|
insertMessage(orgId: string, message: Message): Promise<Message>;
|
|
338
386
|
updateMessage(orgId: string, id: string, patch: Partial<Pick<Message, 'body' | 'editedAt' | 'deletedAt'>>): Promise<Message | null>;
|
|
339
387
|
findMessage(orgId: string, id: string): Promise<Message | null>;
|
|
@@ -341,7 +389,7 @@ interface ChatRepository {
|
|
|
341
389
|
attach(orgId: string, communityId: string, messageId: string, assetIds: string[]): Promise<void>;
|
|
342
390
|
attachmentsOf(orgId: string, messageIds: string[]): Promise<Record<string, string[]>>;
|
|
343
391
|
blockedWith(orgId: string, communityId: string, orgUserId: string, otherIds: string[]): Promise<string[]>;
|
|
344
|
-
setReaction(orgId: string, communityId: string, messageId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<
|
|
392
|
+
setReaction(orgId: string, communityId: string, messageId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<boolean>;
|
|
345
393
|
reactionsOf(orgId: string, messageIds: string[], viewerId: string): Promise<Record<string, MessageReactions>>;
|
|
346
394
|
block(orgId: string, communityId: string, blockerOrgUserId: string, blockedOrgUserId: string): Promise<void>;
|
|
347
395
|
unblock(orgId: string, communityId: string, blockerOrgUserId: string, blockedOrgUserId: string): Promise<void>;
|
|
@@ -365,9 +413,44 @@ interface ChatRepository {
|
|
|
365
413
|
}
|
|
366
414
|
interface ChatWriteScope {
|
|
367
415
|
chats: ChatRepository;
|
|
416
|
+
mentions: MentionsRepository;
|
|
368
417
|
outbox: OutboxAppender;
|
|
369
418
|
}
|
|
370
419
|
type ChatUnitOfWork = UnitOfWork<ChatWriteScope>;
|
|
420
|
+
type SubscriptionInput = {
|
|
421
|
+
state: 'following';
|
|
422
|
+
} | {
|
|
423
|
+
state: 'muted';
|
|
424
|
+
mutedUntil?: Date | null;
|
|
425
|
+
} | {
|
|
426
|
+
state: 'none';
|
|
427
|
+
};
|
|
428
|
+
type MuteInput = Exclude<SubscriptionInput, {
|
|
429
|
+
state: 'following';
|
|
430
|
+
}>;
|
|
431
|
+
interface SubscriptionService {
|
|
432
|
+
setPost(orgId: string, actor: Actor, postId: string, input: SubscriptionInput): Promise<void>;
|
|
433
|
+
setSpace(orgId: string, actor: Actor, spaceId: string, input: MuteInput): Promise<void>;
|
|
434
|
+
setChat(orgId: string, actor: Actor, chatId: string, input: MuteInput): Promise<void>;
|
|
435
|
+
viewerSubscriptions(orgId: string, orgUserId: string, itemType: SubscriptionItemType, itemIds: string[], at: Date): Promise<Record<string, SubscriptionView>>;
|
|
436
|
+
followerIds(orgId: string, postId: string): Promise<string[]>;
|
|
437
|
+
mutedAmong(orgId: string, item: SubjectRef, orgUserIds: string[], at: Date): Promise<string[]>;
|
|
438
|
+
itemSubscriptions(orgId: string, item: SubjectRef): Promise<Subscription[]>;
|
|
439
|
+
}
|
|
440
|
+
interface SubscriptionsRepository {
|
|
441
|
+
upsert(orgId: string, subscription: Subscription): Promise<void>;
|
|
442
|
+
remove(orgId: string, orgUserId: string, itemType: SubscriptionItemType, itemId: string): Promise<void>;
|
|
443
|
+
forViewer(orgId: string, orgUserId: string, itemType: SubscriptionItemType, itemIds: string[]): Promise<Subscription[]>;
|
|
444
|
+
ofItem(orgId: string, itemType: SubscriptionItemType, itemId: string): Promise<Subscription[]>;
|
|
445
|
+
followerIds(orgId: string, postId: string): Promise<string[]>;
|
|
446
|
+
mutedAmong(orgId: string, itemType: SubscriptionItemType, itemId: string, orgUserIds: string[], at: Date): Promise<string[]>;
|
|
447
|
+
}
|
|
448
|
+
interface MentionsRepository {
|
|
449
|
+
ofItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<string[]>;
|
|
450
|
+
forItems(orgId: string, itemType: MentionItemType, itemIds: string[]): Promise<Record<string, string[]>>;
|
|
451
|
+
sync(orgId: string, itemType: MentionItemType, itemId: string, orgUserIds: string[], at: Date): Promise<string[]>;
|
|
452
|
+
removeItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<void>;
|
|
453
|
+
}
|
|
371
454
|
//#endregion
|
|
372
|
-
export { ActivityComments, ActivityGetter, type Actor, AuthorProfileReader, AuthorRecord, AuthorRoleReader, BookmarkQuery, ChatListItem, ChatRepository, ChatService, ChatSummary, ChatUnitOfWork, ChatWriteScope, CommentReactions, CommentWithContext, ContentAccessReader, CreatePostInput, DiscussionRepository, DiscussionService, DiscussionUnitOfWork, DiscussionWriteScope, FeedQuery, MODERATION_QUEUE_LIMIT, MessageQueue, MessageQueueEntry, MessageReactions, PageQuery, PostCommentInput, PostCommentReader, PostMode, PostPatch, PostReactions, PostRepository, PostService, PostThreadsQuery, PostUnitOfWork, PostWriteScope, SendMessageInput, SpaceModerationQueue, StartDirectInput, StartGroupInput, UpdatePostInput };
|
|
455
|
+
export { ActivityComments, ActivityGetter, type Actor, AuthorProfileReader, AuthorRecord, AuthorRoleReader, BookmarkQuery, ChatInboxRow, ChatListItem, ChatRepository, ChatService, ChatSummary, ChatUnitOfWork, ChatUnread, ChatWriteScope, CommentReactions, CommentWithContext, ContentAccessReader, CreatePostInput, DigestRunRepository, DigestScheduleReader, DigestService, DigestUnitOfWork, DigestWriteScope, DiscussionRepository, DiscussionService, DiscussionUnitOfWork, DiscussionWriteScope, FeedQuery, ItemTitle, ItemTitles, MODERATION_QUEUE_LIMIT, MentionsRepository, MessageQueue, MessageQueueEntry, MessageReactions, MuteInput, PageQuery, PostCommentInput, PostCommentReader, PostGuard, PostMode, PostPatch, PostReactions, PostRepository, PostService, PostThreadsQuery, PostUnitOfWork, PostWriteScope, PublishedPost, SendMessageInput, SpaceModerationQueue, StartDirectInput, StartGroupInput, SubscriptionInput, SubscriptionService, SubscriptionsRepository, UpdatePostInput };
|
|
373
456
|
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -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 FlaggedComment,\n JsonValue,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Post,\n PostFlag,\n PostFlagResolution,\n PostStatus,\n PostView,\n} from '../types/index.js';\nimport type { 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 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\n sendDigest(orgId: string, now: Date): Promise<void>;\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 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<void>;\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 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 PostService {\n visibleTitles(orgId: string, actor: Actor, postIds: string[]): Promise<Record<string, string>>;\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 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 follow(orgId: string, postId: string, actor: Actor): Promise<void>;\n unfollow(orgId: string, postId: string, actor: Actor): Promise<void>;\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 followerIds(orgId: string, postId: string): Promise<string[]>;\n moderationQueue(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceModerationQueue>;\n publishDue(orgId: string, now: Date): Promise<void>;\n hasDue(orgId: string, now: Date): Promise<boolean>;\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 find(orgId: string, id: string): Promise<Post | null>;\n findMany(orgId: string, ids: string[]): Promise<Post[]>;\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 /** Due scheduled posts in id order, after `afterId` when given. */\n listDue(orgId: string, now: Date, limit: number, afterId?: string): Promise<Post[]>;\n hasDue(orgId: string, now: Date): Promise<boolean>;\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<void>;\n follow(orgId: string, postId: string, orgUserId: string): Promise<void>;\n unfollow(orgId: string, postId: string, orgUserId: string): Promise<void>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n followedAmong(orgId: string, postIds: string[], orgUserId: string): Promise<string[]>;\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 outbox: OutboxAppender;\n}\nexport type PostUnitOfWork = UnitOfWork<PostWriteScope>;\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 muted: boolean;\n mutedUntil: Date | null;\n unread: number;\n lastMessage: ChatLastMessage | 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 mute(orgId: string, actor: Actor, chatId: string, until?: Date): Promise<void>;\n unmute(orgId: string, actor: Actor, chatId: 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 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<ChatListItem>>;\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 setMuted(\n orgId: string,\n chatId: string,\n orgUserId: string,\n muted: boolean,\n mutedUntil: Date | null,\n ): Promise<void>;\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<void>;\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 outbox: OutboxAppender;\n}\nexport type ChatUnitOfWork = UnitOfWork<ChatWriteScope>;\n"],"mappings":";AAuRA,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 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"}
|
|
@@ -3,7 +3,7 @@ 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 "../shared/ports.js";
|
|
6
|
+
import { Scheduler } from "../shared/ports.js";
|
|
7
7
|
import { FileUrls } from "../assets/file-urls.js";
|
|
8
8
|
import { AssetFiles, MemberImageReader } from "../assets/ports.js";
|
|
9
9
|
import "../assets/index.js";
|
|
@@ -13,7 +13,7 @@ import "../content/index.js";
|
|
|
13
13
|
import { CommunityAccessReader } from "../entitlements/ports.js";
|
|
14
14
|
import "../entitlements/index.js";
|
|
15
15
|
import "./types.js";
|
|
16
|
-
import { AuthorProfileReader, AuthorRoleReader, CreatePostInput, FeedQuery, PageQuery, PostCommentReader, PostReactions, PostRepository, PostService, PostUnitOfWork, SpaceModerationQueue, UpdatePostInput } from "./ports.js";
|
|
16
|
+
import { AuthorProfileReader, AuthorRoleReader, CreatePostInput, FeedQuery, MentionsRepository, PageQuery, PostCommentReader, PostReactions, PostRepository, PostService, PostUnitOfWork, PublishedPost, SpaceModerationQueue, SubscriptionsRepository, UpdatePostInput } from "./ports.js";
|
|
17
17
|
//#region src/discussion/posts.d.ts
|
|
18
18
|
type PostServiceParams = {
|
|
19
19
|
repo: PostRepository;
|
|
@@ -22,9 +22,12 @@ type PostServiceParams = {
|
|
|
22
22
|
authors: AuthorRoleReader;
|
|
23
23
|
profiles: AuthorProfileReader;
|
|
24
24
|
comments: PostCommentReader;
|
|
25
|
+
subscriptions: SubscriptionsRepository;
|
|
26
|
+
mentions: MentionsRepository;
|
|
25
27
|
images: MemberImageReader & Pick<AssetFiles, 'signFile'>;
|
|
26
28
|
fileUrls: FileUrls;
|
|
27
29
|
uow: PostUnitOfWork;
|
|
30
|
+
scheduler: Pick<Scheduler, 'at' | 'cancel'>;
|
|
28
31
|
logger?: Logger;
|
|
29
32
|
};
|
|
30
33
|
declare class PostServiceImpl implements PostService {
|
|
@@ -34,16 +37,24 @@ declare class PostServiceImpl implements PostService {
|
|
|
34
37
|
private readonly authors;
|
|
35
38
|
private readonly profiles;
|
|
36
39
|
private readonly comments;
|
|
40
|
+
private readonly subscriptions;
|
|
41
|
+
private readonly mentions;
|
|
37
42
|
private readonly images;
|
|
38
43
|
private readonly fileUrls;
|
|
39
44
|
private readonly uow;
|
|
45
|
+
private readonly scheduler;
|
|
40
46
|
private readonly logger;
|
|
41
47
|
constructor(params: PostServiceParams);
|
|
42
48
|
visibleTitles(orgId: string, actor: Actor, postIds: string[]): Promise<Record<string, string>>;
|
|
49
|
+
publishedBetween(orgId: string, communityId: string, from: Date, to: Date): Promise<PublishedPost[]>;
|
|
43
50
|
create(orgId: string, actor: Actor, input: CreatePostInput): Promise<PostView>;
|
|
44
51
|
update(orgId: string, postId: string, actor: Actor, patch: UpdatePostInput): Promise<PostView>;
|
|
45
52
|
submit(orgId: string, postId: string, actor: Actor): Promise<PostView>;
|
|
46
53
|
schedule(orgId: string, postId: string, actor: Actor, scheduledAt: Date): Promise<PostView>;
|
|
54
|
+
unschedule(orgId: string, postId: string, actor: Actor): Promise<PostView>;
|
|
55
|
+
publishScheduled(orgId: string, postId: string, now: Date): Promise<void>;
|
|
56
|
+
private arm;
|
|
57
|
+
private disarm;
|
|
47
58
|
approve(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
|
|
48
59
|
remove(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
|
|
49
60
|
restore(orgId: string, postId: string, actor: Actor, communityId?: string): Promise<PostView>;
|
|
@@ -53,8 +64,6 @@ declare class PostServiceImpl implements PostService {
|
|
|
53
64
|
openComments(orgId: string, postId: string, actor: Actor): Promise<PostView>;
|
|
54
65
|
setReactions(orgId: string, postId: string, actor: Actor, enabled: boolean): Promise<PostView>;
|
|
55
66
|
setReaction(orgId: string, postId: string, actor: Actor, emoji: ReactionEmoji | null): Promise<PostReactions>;
|
|
56
|
-
follow(orgId: string, postId: string, actor: Actor): Promise<void>;
|
|
57
|
-
unfollow(orgId: string, postId: string, actor: Actor): Promise<void>;
|
|
58
67
|
bookmark(orgId: string, postId: string, actor: Actor): Promise<void>;
|
|
59
68
|
unbookmark(orgId: string, postId: string, actor: Actor): Promise<void>;
|
|
60
69
|
flag(orgId: string, postId: string, actor: Actor, reason: string): Promise<PostFlag>;
|
|
@@ -72,10 +81,6 @@ declare class PostServiceImpl implements PostService {
|
|
|
72
81
|
space: SpaceDetail;
|
|
73
82
|
}>;
|
|
74
83
|
find(orgId: string, postId: string): Promise<Post | null>;
|
|
75
|
-
followerIds(orgId: string, postId: string): Promise<string[]>;
|
|
76
|
-
hasDue(orgId: string, now: Date): Promise<boolean>;
|
|
77
|
-
publishDue(orgId: string, now: Date): Promise<void>;
|
|
78
|
-
private submitDue;
|
|
79
84
|
private submittable;
|
|
80
85
|
private feedPage;
|
|
81
86
|
private viewOf;
|
|
@@ -96,6 +101,11 @@ declare class PostServiceImpl implements PostService {
|
|
|
96
101
|
private topicFits;
|
|
97
102
|
private assertTopic;
|
|
98
103
|
private patchChanges;
|
|
104
|
+
private mentionable;
|
|
105
|
+
private openersOf;
|
|
106
|
+
private landed;
|
|
107
|
+
private published;
|
|
108
|
+
private updated;
|
|
99
109
|
private meta;
|
|
100
110
|
}
|
|
101
111
|
//#endregion
|