@headless-lms/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/events.d.ts +24 -18
- package/dist/assets/file-urls.d.ts +11 -7
- package/dist/assets/file-urls.js +14 -4
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +5 -4
- package/dist/assets/index.js +4 -3
- 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 +37 -17
- package/dist/assets/service.d.ts +30 -15
- package/dist/assets/service.js +229 -59
- 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 +137 -65
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +20 -20
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +36 -20
- package/dist/automations/community-mail.js +32 -24
- package/dist/automations/community-mail.js.map +1 -1
- package/dist/automations/event-permissions.d.ts +12 -0
- package/dist/automations/event-permissions.js +38 -0
- package/dist/automations/event-permissions.js.map +1 -0
- package/dist/automations/events.d.ts +0 -10
- package/dist/automations/index.d.ts +12 -4
- package/dist/automations/index.js +9 -1
- package/dist/automations/notifications/applicable.d.ts +8 -0
- package/dist/automations/notifications/applicable.js +12 -0
- package/dist/automations/notifications/applicable.js.map +1 -0
- 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 +346 -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 +96 -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 +276 -0
- package/dist/automations/notifications/rules/entitlements.js.map +1 -0
- package/dist/automations/notifications/rules/events.d.ts +9 -0
- package/dist/automations/notifications/rules/events.js +27 -0
- package/dist/automations/notifications/rules/events.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 +35 -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 +98 -0
- package/dist/automations/notifications/rules/recipients.js.map +1 -0
- package/dist/automations/notifications/service.d.ts +29 -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 +560 -0
- package/dist/automations/notifications/types.js.map +1 -0
- package/dist/automations/ports.d.ts +134 -21
- package/dist/automations/service.d.ts +30 -30
- package/dist/automations/service.js +111 -144
- package/dist/automations/service.js.map +1 -1
- package/dist/content/event-sight.d.ts +11 -0
- package/dist/content/event-sight.js +11 -0
- package/dist/content/event-sight.js.map +1 -0
- package/dist/content/events.d.ts +437 -46
- package/dist/content/events.js +45 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +8 -4
- package/dist/content/index.js +5 -1
- package/dist/content/jobs.d.ts +16 -0
- package/dist/content/jobs.js +37 -0
- package/dist/content/jobs.js.map +1 -0
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +37 -0
- package/dist/content/notification-categories.js +73 -0
- package/dist/content/notification-categories.js.map +1 -0
- package/dist/content/ports.d.ts +132 -68
- package/dist/content/service.d.ts +100 -68
- package/dist/content/service.js +703 -146
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +25 -0
- package/dist/content/titles.js +82 -0
- package/dist/content/titles.js.map +1 -0
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +4 -2
- package/dist/discussion/authors.js +9 -5
- package/dist/discussion/authors.js.map +1 -1
- package/dist/discussion/chats.d.ts +63 -39
- package/dist/discussion/chats.js +455 -190
- 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 +227 -23
- package/dist/discussion/events.js +114 -19
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +12 -4
- package/dist/discussion/index.js +9 -2
- 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/live.d.ts +106 -0
- package/dist/discussion/live.js +642 -0
- package/dist/discussion/live.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 +279 -114
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +60 -44
- package/dist/discussion/posts.js +453 -287
- package/dist/discussion/posts.js.map +1 -1
- package/dist/discussion/rules.d.ts +18 -0
- package/dist/discussion/rules.js +22 -0
- package/dist/discussion/rules.js.map +1 -0
- package/dist/discussion/service.d.ts +50 -35
- package/dist/discussion/service.js +333 -205
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +37 -0
- package/dist/discussion/subscriptions.js +115 -0
- package/dist/discussion/subscriptions.js.map +1 -0
- package/dist/discussion/titles.d.ts +28 -0
- package/dist/discussion/titles.js +98 -0
- package/dist/discussion/titles.js.map +1 -0
- package/dist/entitlements/events.d.ts +20 -20
- package/dist/entitlements/index.d.ts +5 -4
- package/dist/entitlements/index.js +3 -2
- 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/model.d.ts +2 -2
- package/dist/entitlements/ports.d.ts +68 -38
- package/dist/entitlements/rules.d.ts +3 -6
- package/dist/entitlements/rules.js +5 -10
- package/dist/entitlements/rules.js.map +1 -1
- package/dist/entitlements/service.d.ts +43 -28
- package/dist/entitlements/service.js +387 -160
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +30 -0
- package/dist/identity/devices.js +109 -0
- package/dist/identity/devices.js.map +1 -0
- package/dist/identity/index.d.ts +6 -5
- package/dist/identity/index.js +4 -3
- package/dist/identity/model.d.ts +2 -2
- package/dist/identity/notification-settings.d.ts +28 -0
- package/dist/identity/notification-settings.js +114 -0
- package/dist/identity/notification-settings.js.map +1 -0
- package/dist/identity/ports.d.ts +80 -56
- package/dist/identity/service.d.ts +8 -7
- package/dist/identity/service.js +17 -13
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/ports.d.ts +9 -8
- package/dist/integrations/service.d.ts +9 -8
- package/dist/integrations/service.js +33 -16
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/default-roles.d.ts +22 -0
- package/dist/organizations/default-roles.js +89 -0
- package/dist/organizations/default-roles.js.map +1 -0
- package/dist/organizations/effective-permissions.d.ts +45 -0
- package/dist/organizations/effective-permissions.js +113 -0
- package/dist/organizations/effective-permissions.js.map +1 -0
- package/dist/organizations/events.d.ts +320 -181
- package/dist/organizations/events.js +90 -18
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +15 -10
- package/dist/organizations/index.js +8 -3
- package/dist/organizations/members.d.ts +66 -8
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/notification-catalog.d.ts +31 -0
- package/dist/organizations/notification-catalog.js +32 -0
- package/dist/organizations/notification-catalog.js.map +1 -0
- package/dist/organizations/notification-categories.d.ts +34 -0
- package/dist/organizations/notification-categories.js +66 -0
- package/dist/organizations/notification-categories.js.map +1 -0
- package/dist/organizations/org-roles.d.ts +33 -0
- package/dist/organizations/org-roles.js +49 -0
- package/dist/organizations/org-roles.js.map +1 -0
- package/dist/organizations/permissions.d.ts +311 -0
- package/dist/organizations/permissions.js +327 -0
- package/dist/organizations/permissions.js.map +1 -0
- package/dist/organizations/ports.d.ts +187 -65
- package/dist/organizations/service.d.ts +104 -35
- package/dist/organizations/service.js +1247 -323
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/testing.d.ts +18 -0
- package/dist/organizations/testing.js +37 -0
- package/dist/organizations/testing.js.map +1 -0
- package/dist/organizations/types.d.ts +5 -16
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +11 -3
- package/dist/progress/service.d.ts +6 -5
- package/dist/progress/service.js +23 -8
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +6 -4
- package/dist/reporting/admin/model.d.ts +36 -3
- package/dist/reporting/admin/ports.d.ts +39 -14
- package/dist/reporting/admin/service.d.ts +32 -11
- package/dist/reporting/admin/service.js +102 -18
- package/dist/reporting/admin/service.js.map +1 -1
- package/dist/reporting/events/index.d.ts +27 -0
- package/dist/reporting/events/index.js +20 -0
- package/dist/reporting/events/index.js.map +1 -0
- package/dist/reporting/member/calendar.d.ts +11 -0
- package/dist/reporting/member/calendar.js +75 -0
- package/dist/reporting/member/calendar.js.map +1 -0
- package/dist/reporting/member/index.d.ts +3 -2
- package/dist/reporting/member/model.d.ts +80 -26
- package/dist/reporting/member/ports.d.ts +50 -28
- package/dist/reporting/member/service.d.ts +81 -35
- package/dist/reporting/member/service.js +438 -145
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +22 -3
- package/dist/shared/errors.d.ts +15 -2
- package/dist/shared/errors.js +21 -2
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +7 -0
- package/dist/shared/id.js +8 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/logger.d.ts +2 -1
- package/dist/shared/logger.js +10 -1
- package/dist/shared/logger.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 +68 -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 +4 -5
- package/dist/types/email-templates.d.ts +119 -6
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +14 -17
- package/dist/types/index.d.ts +13 -12
- package/dist/types/index.js +2 -1
- package/dist/types/notifications.d.ts +157 -0
- package/dist/types/notifications.js +1 -0
- package/dist/types/organizations.d.ts +3 -2
- package/dist/types/ports.d.ts +8 -1
- package/dist/types/ports.js +7 -1
- package/dist/types/ports.js.map +1 -0
- package/dist/types/schemas/assets.d.ts +5 -5
- package/dist/types/schemas/assets.js +4 -3
- package/dist/types/schemas/assets.js.map +1 -1
- 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 +596 -11
- package/dist/types/schemas/content.js +225 -9
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +362 -66
- package/dist/types/schemas/discussion.js +123 -15
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +14 -1
- package/dist/types/schemas/entitlements.js +11 -4
- package/dist/types/schemas/entitlements.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 +8 -7
- package/dist/types/schemas/index.js +8 -7
- package/dist/types/schemas/notifications.d.ts +96 -0
- package/dist/types/schemas/notifications.js +57 -0
- package/dist/types/schemas/notifications.js.map +1 -0
- package/dist/types/schemas/organizations.d.ts +96 -60
- package/dist/types/schemas/organizations.js +78 -27
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +0 -1
- package/dist/types/schemas/progress.js +0 -1
- package/dist/types/schemas/progress.js.map +1 -1
- 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
- package/dist/organizations/access-scopes.d.ts +0 -19
- package/dist/organizations/access-scopes.js +0 -89
- package/dist/organizations/access-scopes.js.map +0 -1
- package/dist/organizations/roles.d.ts +0 -19
- package/dist/organizations/roles.js +0 -82
- package/dist/organizations/roles.js.map +0 -1
package/dist/discussion/posts.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
|
-
import { genId } from "../shared/id.js";
|
|
2
|
-
import { ConflictError, ForbiddenError, InvalidInputError, NotFoundError } from "../shared/errors.js";
|
|
3
1
|
import { noopLogger } from "../shared/logger.js";
|
|
4
|
-
import {
|
|
2
|
+
import { ConflictError, ForbiddenError, InvalidInputError, NotFoundError, UnavailableError } from "../shared/errors.js";
|
|
3
|
+
import { genId } from "../shared/id.js";
|
|
5
4
|
import { discussionEvents } from "./events.js";
|
|
6
5
|
import { authorView } from "./authors.js";
|
|
6
|
+
import { keepAllowed, mentionTokens, mentionViews } from "./mentions.js";
|
|
7
|
+
import { skipsReview, teamBadge } from "./rules.js";
|
|
8
|
+
import { POST_PUBLISH_JOB } from "./jobs.js";
|
|
9
|
+
import { activeSubscription } from "./subscriptions.js";
|
|
7
10
|
import "./ports.js";
|
|
8
11
|
//#region src/discussion/posts.ts
|
|
12
|
+
const MAX_BODY_ASSETS = 20;
|
|
13
|
+
const INLINE_TYPES = /^(image\/(png|jpeg|gif|webp|avif)|video\/.+|audio\/.+|application\/pdf)$/i;
|
|
9
14
|
const PUBLISHED_ONLY = ["published"];
|
|
10
|
-
const DUE_BATCH_SIZE = 100;
|
|
11
15
|
const MODERATOR_VISIBLE = [
|
|
12
16
|
"published",
|
|
13
17
|
"pending",
|
|
14
18
|
"removed"
|
|
15
19
|
];
|
|
20
|
+
const DRAFT = {
|
|
21
|
+
status: "draft",
|
|
22
|
+
scheduledAt: null,
|
|
23
|
+
publishedAt: null,
|
|
24
|
+
removedBy: null
|
|
25
|
+
};
|
|
16
26
|
var PostServiceImpl = class {
|
|
17
27
|
repo;
|
|
18
28
|
access;
|
|
@@ -20,9 +30,13 @@ var PostServiceImpl = class {
|
|
|
20
30
|
authors;
|
|
21
31
|
profiles;
|
|
22
32
|
comments;
|
|
33
|
+
subscriptions;
|
|
34
|
+
mentions;
|
|
23
35
|
images;
|
|
24
36
|
fileUrls;
|
|
37
|
+
bodies;
|
|
25
38
|
uow;
|
|
39
|
+
scheduler;
|
|
26
40
|
logger;
|
|
27
41
|
constructor(params) {
|
|
28
42
|
this.repo = params.repo;
|
|
@@ -31,20 +45,31 @@ var PostServiceImpl = class {
|
|
|
31
45
|
this.authors = params.authors;
|
|
32
46
|
this.profiles = params.profiles;
|
|
33
47
|
this.comments = params.comments;
|
|
48
|
+
this.subscriptions = params.subscriptions;
|
|
49
|
+
this.mentions = params.mentions;
|
|
34
50
|
this.images = params.images;
|
|
35
51
|
this.fileUrls = params.fileUrls;
|
|
52
|
+
this.bodies = params.bodies;
|
|
36
53
|
this.uow = params.uow;
|
|
54
|
+
this.scheduler = params.scheduler;
|
|
37
55
|
this.logger = params.logger ?? noopLogger;
|
|
38
56
|
}
|
|
39
|
-
async visibleTitles(
|
|
57
|
+
async visibleTitles(postIds, actor) {
|
|
58
|
+
const orgId = actor.orgId;
|
|
40
59
|
const found = await this.repo.findMany(orgId, [...new Set(postIds)]);
|
|
41
60
|
const communityIds = [...new Set(found.map((post) => post.communityId))];
|
|
42
|
-
const open = new Set((await Promise.all(communityIds.map((communityId) => this.access.openSpaceIds(
|
|
61
|
+
const open = new Set((await Promise.all(communityIds.map((communityId) => this.access.openSpaceIds(communityId, actor)))).flat());
|
|
43
62
|
return Object.fromEntries(found.filter((post) => open.has(post.spaceId) && this.canSee(actor, post)).map((post) => [post.id, post.title]));
|
|
44
63
|
}
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
publishedBetween(orgId, communityId, from, to) {
|
|
65
|
+
return this.repo.publishedBetween(orgId, communityId, from, to);
|
|
66
|
+
}
|
|
67
|
+
async create(input, actor) {
|
|
68
|
+
const orgId = actor.orgId;
|
|
69
|
+
const space = await this.requireOpen(actor, input.spaceId, input.communityId);
|
|
47
70
|
await this.requireOwnedImage(orgId, actor.id, input.coverAssetId);
|
|
71
|
+
const parsed = this.bodies.parse(input.body);
|
|
72
|
+
await this.requireBodyAssets(orgId, actor.id, parsed.assetIds);
|
|
48
73
|
this.assertTopic(space, input.topicId);
|
|
49
74
|
const now = /* @__PURE__ */ new Date();
|
|
50
75
|
const landing = this.initial(input, space, actor, now);
|
|
@@ -66,18 +91,16 @@ var PostServiceImpl = class {
|
|
|
66
91
|
updatedAt: now,
|
|
67
92
|
...landing
|
|
68
93
|
};
|
|
69
|
-
const
|
|
94
|
+
const mentioned = await this.mentionable(orgId, space.id, post.title, parsed.mentions);
|
|
95
|
+
const saved = await this.uow.run(async ({ posts, mentions, outbox }) => {
|
|
70
96
|
const created = await posts.insert(orgId, post);
|
|
97
|
+
await mentions.sync(orgId, "post", created.id, mentioned, now);
|
|
71
98
|
const events = [discussionEvents.postCreated.make({
|
|
72
99
|
orgId,
|
|
73
100
|
data: created,
|
|
74
101
|
metadata: this.meta(actor, created.status === "pending" ? null : void 0)
|
|
75
102
|
})];
|
|
76
|
-
if (created.status === "published") events.push(
|
|
77
|
-
orgId,
|
|
78
|
-
data: created,
|
|
79
|
-
metadata: this.meta(actor, null)
|
|
80
|
-
}));
|
|
103
|
+
if (created.status === "published") events.push(this.published(orgId, created, this.meta(actor, null), mentioned));
|
|
81
104
|
await outbox.append(events);
|
|
82
105
|
return created;
|
|
83
106
|
});
|
|
@@ -87,22 +110,26 @@ var PostServiceImpl = class {
|
|
|
87
110
|
status: saved.status,
|
|
88
111
|
actorId: actor.id
|
|
89
112
|
});
|
|
90
|
-
|
|
113
|
+
if (saved.status === "scheduled") await this.arm(orgId, actor, saved, null);
|
|
114
|
+
await this.syncCover(saved);
|
|
115
|
+
return this.viewOf(actor, saved);
|
|
91
116
|
}
|
|
92
|
-
async update(
|
|
93
|
-
const
|
|
117
|
+
async update(postId, patch, actor) {
|
|
118
|
+
const orgId = actor.orgId;
|
|
119
|
+
const { post, space } = await this.loadVisible(actor, postId);
|
|
94
120
|
this.assertAuthor(actor, post);
|
|
95
121
|
if (post.status === "removed") throw new ConflictError("This post was removed");
|
|
96
122
|
if (patch.topicId !== void 0) this.assertTopic(space, patch.topicId);
|
|
97
123
|
if (typeof patch.coverAssetId === "string") await this.requireOwnedImage(orgId, actor.id, patch.coverAssetId);
|
|
98
|
-
|
|
99
|
-
|
|
124
|
+
const parsed = this.bodies.parse(patch.body ?? post.body);
|
|
125
|
+
if (patch.body !== void 0) await this.requireBodyAssets(orgId, actor.id, parsed.assetIds);
|
|
126
|
+
if (!this.patchChanges(post, patch)) return this.viewOf(actor, post);
|
|
127
|
+
const retext = patch.title !== void 0 || patch.body !== void 0;
|
|
128
|
+
const candidates = retext ? await this.mentionable(orgId, post.spaceId, patch.title ?? post.title, parsed.mentions) : [];
|
|
129
|
+
const updated = await this.uow.run(async ({ posts, mentions, outbox }) => {
|
|
100
130
|
const next = await this.mustUpdate(posts, orgId, postId, patch);
|
|
101
|
-
await
|
|
102
|
-
|
|
103
|
-
data: next,
|
|
104
|
-
metadata: this.meta(actor, next.status === "pending" ? post.status : void 0)
|
|
105
|
-
})]);
|
|
131
|
+
const added = retext ? await mentions.sync(orgId, "post", postId, candidates, /* @__PURE__ */ new Date()) : [];
|
|
132
|
+
await outbox.append([this.updated(orgId, next, this.meta(actor, next.status === "pending" ? post.status : void 0), next.status === "published" ? keepAllowed(candidates, added) : [])]);
|
|
106
133
|
return next;
|
|
107
134
|
});
|
|
108
135
|
this.logger.info("post updated", {
|
|
@@ -111,39 +138,41 @@ var PostServiceImpl = class {
|
|
|
111
138
|
fields: Object.keys(patch),
|
|
112
139
|
actorId: actor.id
|
|
113
140
|
});
|
|
114
|
-
|
|
141
|
+
await this.syncCover(updated);
|
|
142
|
+
return this.viewOf(actor, updated);
|
|
115
143
|
}
|
|
116
|
-
async submit(
|
|
117
|
-
const
|
|
144
|
+
async submit(postId, actor) {
|
|
145
|
+
const orgId = actor.orgId;
|
|
146
|
+
const { post, space } = await this.loadVisible(actor, postId);
|
|
118
147
|
this.assertAuthor(actor, post);
|
|
119
148
|
if (post.status !== "draft" && post.status !== "scheduled") throw new ConflictError("This post cannot be submitted");
|
|
120
149
|
this.assertTopic(space, post.topicId);
|
|
121
|
-
const saved = await this.commitSubmission(orgId, post, space, actor.
|
|
150
|
+
const saved = await this.commitSubmission(orgId, post, space, skipsReview(actor.permissions, { communityId: post.communityId }), actor, /* @__PURE__ */ new Date());
|
|
122
151
|
this.logger.info("post submitted", {
|
|
123
152
|
orgId,
|
|
124
153
|
postId,
|
|
125
154
|
status: saved.status,
|
|
126
155
|
actorId: actor.id
|
|
127
156
|
});
|
|
128
|
-
|
|
157
|
+
if (post.status === "scheduled") await this.disarm(orgId, postId);
|
|
158
|
+
await this.syncCover(saved);
|
|
159
|
+
return this.viewOf(actor, saved);
|
|
129
160
|
}
|
|
130
|
-
async schedule(
|
|
131
|
-
const
|
|
161
|
+
async schedule(postId, scheduledAt, actor) {
|
|
162
|
+
const orgId = actor.orgId;
|
|
163
|
+
const { post } = await this.loadVisible(actor, postId);
|
|
132
164
|
this.assertAuthor(actor, post);
|
|
133
165
|
if (post.status === "published" || post.status === "removed" || post.status === "pending") throw new ConflictError("This post cannot be scheduled");
|
|
134
166
|
if (scheduledAt <= /* @__PURE__ */ new Date()) throw new InvalidInputError("A scheduled time must be in the future");
|
|
135
167
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
136
|
-
const next = await
|
|
168
|
+
const next = await posts.updateIf(orgId, postId, { status: post.status }, {
|
|
137
169
|
status: "scheduled",
|
|
138
170
|
scheduledAt,
|
|
139
171
|
publishedAt: null,
|
|
140
172
|
removedBy: null
|
|
141
173
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
data: next,
|
|
145
|
-
metadata: this.meta(actor, post.status)
|
|
146
|
-
})]);
|
|
174
|
+
if (!next) throw new ConflictError("This post cannot be scheduled");
|
|
175
|
+
await outbox.append([this.updated(orgId, next, this.meta(actor, post.status))]);
|
|
147
176
|
return next;
|
|
148
177
|
});
|
|
149
178
|
this.logger.info("post scheduled", {
|
|
@@ -152,13 +181,134 @@ var PostServiceImpl = class {
|
|
|
152
181
|
scheduledAt: scheduledAt.toISOString(),
|
|
153
182
|
actorId: actor.id
|
|
154
183
|
});
|
|
155
|
-
|
|
184
|
+
await this.arm(orgId, actor, updated, post.status === "scheduled" ? post.scheduledAt : null);
|
|
185
|
+
await this.syncCover(updated);
|
|
186
|
+
return this.viewOf(actor, updated);
|
|
187
|
+
}
|
|
188
|
+
async unschedule(postId, actor) {
|
|
189
|
+
const orgId = actor.orgId;
|
|
190
|
+
const { post } = await this.loadVisible(actor, postId);
|
|
191
|
+
this.assertAuthor(actor, post);
|
|
192
|
+
if (post.status !== "scheduled") throw new ConflictError("This post is not scheduled");
|
|
193
|
+
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
194
|
+
const next = await posts.updateIf(orgId, postId, { status: "scheduled" }, DRAFT);
|
|
195
|
+
if (!next) throw new ConflictError("This post is not scheduled");
|
|
196
|
+
await outbox.append([this.updated(orgId, next, this.meta(actor, post.status))]);
|
|
197
|
+
return next;
|
|
198
|
+
});
|
|
199
|
+
this.logger.info("post unscheduled", {
|
|
200
|
+
orgId,
|
|
201
|
+
postId,
|
|
202
|
+
actorId: actor.id
|
|
203
|
+
});
|
|
204
|
+
await this.disarm(orgId, postId);
|
|
205
|
+
await this.syncCover(updated);
|
|
206
|
+
return this.viewOf(actor, updated);
|
|
156
207
|
}
|
|
157
|
-
async
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
208
|
+
async publishScheduled(orgId, postId, now) {
|
|
209
|
+
const post = await this.repo.find(orgId, postId);
|
|
210
|
+
if (!post || post.status !== "scheduled" || post.scheduledAt === null) {
|
|
211
|
+
this.logger.debug("scheduled post no longer scheduled", {
|
|
212
|
+
orgId,
|
|
213
|
+
postId
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (post.scheduledAt > now) {
|
|
218
|
+
await this.scheduler.at(POST_PUBLISH_JOB, post.scheduledAt, postId, {
|
|
219
|
+
orgId,
|
|
220
|
+
postId
|
|
221
|
+
});
|
|
222
|
+
this.logger.info("scheduled post timer moved", {
|
|
223
|
+
orgId,
|
|
224
|
+
postId,
|
|
225
|
+
scheduledAt: post.scheduledAt.toISOString()
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const space = await this.content.getSpace(post.spaceId, this.authors.systemActor(orgId));
|
|
230
|
+
if (!space) {
|
|
231
|
+
this.logger.error("scheduled post has no space", {
|
|
232
|
+
orgId,
|
|
233
|
+
postId,
|
|
234
|
+
spaceId: post.spaceId
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const author = await this.authors.actorFor(orgId, post.orgUserId);
|
|
239
|
+
const refused = author === null || !await this.submittable(post, space, author);
|
|
240
|
+
const patch = author === null || refused ? DRAFT : this.submissionOf(space.settings.requireReview, skipsReview(author.permissions, { communityId: post.communityId }), now);
|
|
241
|
+
const mentioned = await this.mentionedOpeners(orgId, post);
|
|
242
|
+
const saved = await this.uow.run(async ({ posts, outbox }) => {
|
|
243
|
+
const next = await posts.updateIf(orgId, postId, {
|
|
244
|
+
status: "scheduled",
|
|
245
|
+
dueBy: now
|
|
246
|
+
}, patch);
|
|
247
|
+
if (!next) return null;
|
|
248
|
+
await outbox.append([this.landed(orgId, next, this.meta(null, post.status), mentioned)]);
|
|
249
|
+
return next;
|
|
250
|
+
});
|
|
251
|
+
if (!saved) {
|
|
252
|
+
this.logger.debug("scheduled post already handled", {
|
|
253
|
+
orgId,
|
|
254
|
+
postId
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
this.logger.info("scheduled post submitted", {
|
|
259
|
+
orgId,
|
|
260
|
+
postId,
|
|
261
|
+
status: saved.status,
|
|
262
|
+
refused
|
|
263
|
+
});
|
|
264
|
+
await this.syncCover(saved);
|
|
265
|
+
}
|
|
266
|
+
async arm(orgId, actor, post, previousScheduledAt) {
|
|
267
|
+
if (post.scheduledAt === null) return;
|
|
268
|
+
try {
|
|
269
|
+
await this.scheduler.at(POST_PUBLISH_JOB, post.scheduledAt, post.id, {
|
|
270
|
+
orgId,
|
|
271
|
+
postId: post.id
|
|
272
|
+
});
|
|
273
|
+
return;
|
|
274
|
+
} catch (err) {
|
|
275
|
+
this.logger.error("post timer not set", {
|
|
276
|
+
orgId,
|
|
277
|
+
postId: post.id,
|
|
278
|
+
err
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
await this.uow.run(async ({ posts, outbox }) => {
|
|
282
|
+
const reverted = await posts.updateIf(orgId, post.id, {
|
|
283
|
+
status: "scheduled",
|
|
284
|
+
scheduledAt: post.scheduledAt
|
|
285
|
+
}, previousScheduledAt ? { scheduledAt: previousScheduledAt } : DRAFT);
|
|
286
|
+
if (!reverted) return;
|
|
287
|
+
await outbox.append([this.updated(orgId, reverted, this.meta(actor, reverted.status === "draft" ? post.status : void 0))]);
|
|
288
|
+
});
|
|
289
|
+
throw new UnavailableError("The post could not be scheduled; try again", { postId: post.id });
|
|
290
|
+
}
|
|
291
|
+
syncCover(post) {
|
|
292
|
+
return this.images.syncPublicImage(post.orgId, post.id, post.status === "published" ? post.coverAssetId : null);
|
|
293
|
+
}
|
|
294
|
+
async disarm(orgId, postId) {
|
|
295
|
+
try {
|
|
296
|
+
await this.scheduler.cancel(POST_PUBLISH_JOB, postId);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
this.logger.warn("post timer not cancelled", {
|
|
299
|
+
orgId,
|
|
300
|
+
postId,
|
|
301
|
+
err
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async approve(postId, communityId, actor) {
|
|
306
|
+
const orgId = actor.orgId;
|
|
307
|
+
const { post } = await this.loadVisible(actor, postId, communityId);
|
|
308
|
+
this.requireModerator(actor, post, "approve");
|
|
160
309
|
if (post.status !== "pending") throw new ConflictError("This post is not awaiting review");
|
|
161
310
|
const now = /* @__PURE__ */ new Date();
|
|
311
|
+
const mentioned = await this.mentionedOpeners(orgId, post);
|
|
162
312
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
163
313
|
const next = await this.mustUpdate(posts, orgId, postId, {
|
|
164
314
|
status: "published",
|
|
@@ -166,11 +316,7 @@ var PostServiceImpl = class {
|
|
|
166
316
|
scheduledAt: null,
|
|
167
317
|
removedBy: null
|
|
168
318
|
});
|
|
169
|
-
await outbox.append([
|
|
170
|
-
orgId,
|
|
171
|
-
data: next,
|
|
172
|
-
metadata: this.meta(actor, post.status)
|
|
173
|
-
})]);
|
|
319
|
+
await outbox.append([this.landed(orgId, next, this.meta(actor, post.status), mentioned)]);
|
|
174
320
|
return next;
|
|
175
321
|
});
|
|
176
322
|
this.logger.info("post approved", {
|
|
@@ -178,11 +324,13 @@ var PostServiceImpl = class {
|
|
|
178
324
|
postId,
|
|
179
325
|
actorId: actor.id
|
|
180
326
|
});
|
|
181
|
-
|
|
327
|
+
await this.syncCover(updated);
|
|
328
|
+
return this.viewOf(actor, updated);
|
|
182
329
|
}
|
|
183
|
-
async remove(
|
|
184
|
-
const
|
|
185
|
-
|
|
330
|
+
async remove(postId, communityId, actor) {
|
|
331
|
+
const orgId = actor.orgId;
|
|
332
|
+
const { post } = await this.loadVisible(actor, postId, communityId);
|
|
333
|
+
if (post.orgUserId !== actor.id) this.requireModerator(actor, post, "remove");
|
|
186
334
|
if (post.status === "removed") throw new ConflictError("This post was removed");
|
|
187
335
|
if (post.status !== "published" && post.status !== "pending") throw new ConflictError("This post cannot be removed");
|
|
188
336
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
@@ -207,27 +355,21 @@ var PostServiceImpl = class {
|
|
|
207
355
|
postId,
|
|
208
356
|
actorId: actor.id
|
|
209
357
|
});
|
|
210
|
-
|
|
358
|
+
await this.syncCover(updated);
|
|
359
|
+
return this.viewOf(actor, updated);
|
|
211
360
|
}
|
|
212
|
-
async restore(
|
|
213
|
-
const
|
|
214
|
-
|
|
361
|
+
async restore(postId, communityId, actor) {
|
|
362
|
+
const orgId = actor.orgId;
|
|
363
|
+
const { post, space } = await this.loadVisible(actor, postId, communityId);
|
|
364
|
+
this.requireModerator(actor, post, "restore");
|
|
215
365
|
if (post.status !== "removed") throw new ConflictError("This post was not removed");
|
|
216
366
|
const now = /* @__PURE__ */ new Date();
|
|
217
|
-
const
|
|
367
|
+
const author = (await this.authors.permissionsOfMany(orgId, [post.orgUserId])).get(post.orgUserId);
|
|
368
|
+
const skips = author !== void 0 && skipsReview(author, { communityId: post.communityId });
|
|
218
369
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
219
|
-
const next = await this.mustUpdate(posts, orgId, postId, this.submissionOf(space.settings.requireReview,
|
|
370
|
+
const next = await this.mustUpdate(posts, orgId, postId, this.submissionOf(space.settings.requireReview, skips, now));
|
|
220
371
|
const metadata = this.meta(actor, post.status);
|
|
221
|
-
|
|
222
|
-
orgId,
|
|
223
|
-
data: next,
|
|
224
|
-
metadata
|
|
225
|
-
}) : discussionEvents.postUpdated.make({
|
|
226
|
-
orgId,
|
|
227
|
-
data: next,
|
|
228
|
-
metadata
|
|
229
|
-
});
|
|
230
|
-
await outbox.append([event]);
|
|
372
|
+
await outbox.append([next.status === "published" ? this.published(orgId, next, metadata, []) : this.updated(orgId, next, metadata)]);
|
|
231
373
|
return next;
|
|
232
374
|
});
|
|
233
375
|
this.logger.info("post restored", {
|
|
@@ -236,27 +378,42 @@ var PostServiceImpl = class {
|
|
|
236
378
|
status: updated.status,
|
|
237
379
|
actorId: actor.id
|
|
238
380
|
});
|
|
239
|
-
|
|
381
|
+
await this.syncCover(updated);
|
|
382
|
+
return this.viewOf(actor, updated);
|
|
240
383
|
}
|
|
241
|
-
pin(
|
|
242
|
-
return this.moderate(
|
|
384
|
+
pin(postId, actor) {
|
|
385
|
+
return this.moderate(postId, actor, { pinned: true }, (post) => post.pinned, "post pinned");
|
|
243
386
|
}
|
|
244
|
-
unpin(
|
|
245
|
-
return this.moderate(
|
|
387
|
+
unpin(postId, actor) {
|
|
388
|
+
return this.moderate(postId, actor, { pinned: false }, (post) => !post.pinned, "post unpinned");
|
|
246
389
|
}
|
|
247
|
-
closeComments(
|
|
248
|
-
return this.moderate(
|
|
390
|
+
closeComments(postId, actor) {
|
|
391
|
+
return this.moderate(postId, actor, { commentsClosed: true }, (post) => post.commentsClosed === true, "post comments closed");
|
|
249
392
|
}
|
|
250
|
-
openComments(
|
|
251
|
-
return this.moderate(
|
|
393
|
+
openComments(postId, actor) {
|
|
394
|
+
return this.moderate(postId, actor, { commentsClosed: false }, (post) => post.commentsClosed === false, "post comments opened");
|
|
252
395
|
}
|
|
253
|
-
setReactions(
|
|
254
|
-
return this.moderate(
|
|
396
|
+
setReactions(postId, enabled, actor) {
|
|
397
|
+
return this.moderate(postId, actor, { reactionsEnabled: enabled }, (post) => post.reactionsEnabled === enabled, "post reactions changed");
|
|
255
398
|
}
|
|
256
|
-
async setReaction(
|
|
257
|
-
const
|
|
399
|
+
async setReaction(postId, emoji, actor) {
|
|
400
|
+
const orgId = actor.orgId;
|
|
401
|
+
const { post, space } = await this.loadVisible(actor, postId);
|
|
258
402
|
if (!(post.reactionsEnabled ?? space.settings.reactions)) throw new ForbiddenError("Reactions are off on this post");
|
|
259
|
-
await this.
|
|
403
|
+
await this.uow.run(async ({ posts, outbox }) => {
|
|
404
|
+
if (!await posts.setReaction(orgId, postId, actor.id, emoji) || emoji === null) return;
|
|
405
|
+
await outbox.append([discussionEvents.postReactionAdded.make({
|
|
406
|
+
orgId,
|
|
407
|
+
data: {
|
|
408
|
+
postId,
|
|
409
|
+
spaceId: post.spaceId,
|
|
410
|
+
communityId: post.communityId,
|
|
411
|
+
orgUserId: actor.id,
|
|
412
|
+
reaction: emoji
|
|
413
|
+
},
|
|
414
|
+
metadata: this.meta(actor)
|
|
415
|
+
})]);
|
|
416
|
+
});
|
|
260
417
|
this.logger.debug("post reaction set", {
|
|
261
418
|
orgId,
|
|
262
419
|
postId,
|
|
@@ -264,26 +421,9 @@ var PostServiceImpl = class {
|
|
|
264
421
|
});
|
|
265
422
|
return (await this.repo.reactionsOf(orgId, [postId], actor.id))[postId] ?? { reactions: {} };
|
|
266
423
|
}
|
|
267
|
-
async
|
|
268
|
-
|
|
269
|
-
await this.
|
|
270
|
-
this.logger.debug("post followed", {
|
|
271
|
-
orgId,
|
|
272
|
-
postId,
|
|
273
|
-
orgUserId: actor.id
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
async unfollow(orgId, postId, actor) {
|
|
277
|
-
await this.loadVisible(orgId, actor, postId);
|
|
278
|
-
await this.repo.unfollow(orgId, postId, actor.id);
|
|
279
|
-
this.logger.debug("post unfollowed", {
|
|
280
|
-
orgId,
|
|
281
|
-
postId,
|
|
282
|
-
orgUserId: actor.id
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
async bookmark(orgId, postId, actor) {
|
|
286
|
-
await this.loadVisible(orgId, actor, postId);
|
|
424
|
+
async bookmark(postId, actor) {
|
|
425
|
+
const orgId = actor.orgId;
|
|
426
|
+
await this.loadVisible(actor, postId);
|
|
287
427
|
await this.repo.bookmark(orgId, postId, actor.id);
|
|
288
428
|
this.logger.debug("post bookmarked", {
|
|
289
429
|
orgId,
|
|
@@ -291,8 +431,9 @@ var PostServiceImpl = class {
|
|
|
291
431
|
orgUserId: actor.id
|
|
292
432
|
});
|
|
293
433
|
}
|
|
294
|
-
async unbookmark(
|
|
295
|
-
|
|
434
|
+
async unbookmark(postId, actor) {
|
|
435
|
+
const orgId = actor.orgId;
|
|
436
|
+
await this.loadVisible(actor, postId);
|
|
296
437
|
await this.repo.unbookmark(orgId, postId, actor.id);
|
|
297
438
|
this.logger.debug("post unbookmarked", {
|
|
298
439
|
orgId,
|
|
@@ -300,8 +441,9 @@ var PostServiceImpl = class {
|
|
|
300
441
|
orgUserId: actor.id
|
|
301
442
|
});
|
|
302
443
|
}
|
|
303
|
-
async flag(
|
|
304
|
-
const
|
|
444
|
+
async flag(postId, reason, actor) {
|
|
445
|
+
const orgId = actor.orgId;
|
|
446
|
+
const { post } = await this.loadVisible(actor, postId);
|
|
305
447
|
const now = /* @__PURE__ */ new Date();
|
|
306
448
|
const flag = {
|
|
307
449
|
orgId,
|
|
@@ -340,9 +482,10 @@ var PostServiceImpl = class {
|
|
|
340
482
|
});
|
|
341
483
|
return saved;
|
|
342
484
|
}
|
|
343
|
-
async resolveFlags(
|
|
344
|
-
|
|
345
|
-
|
|
485
|
+
async resolveFlags(postId, resolution, communityId, actor) {
|
|
486
|
+
const orgId = actor.orgId;
|
|
487
|
+
const { post } = await this.loadVisible(actor, postId, communityId);
|
|
488
|
+
this.requireModerator(actor, post, "resolve flags");
|
|
346
489
|
await this.repo.resolveFlags(orgId, postId, {
|
|
347
490
|
resolvedAt: /* @__PURE__ */ new Date(),
|
|
348
491
|
resolvedBy: actor.id,
|
|
@@ -355,10 +498,11 @@ var PostServiceImpl = class {
|
|
|
355
498
|
actorId: actor.id
|
|
356
499
|
});
|
|
357
500
|
}
|
|
358
|
-
async spaceFeed(
|
|
359
|
-
const
|
|
501
|
+
async spaceFeed(communityId, spaceId, query, actor) {
|
|
502
|
+
const orgId = actor.orgId;
|
|
503
|
+
const space = await this.requireOpen(actor, spaceId, communityId);
|
|
360
504
|
if (query.topicId && !space.topics.some((topic) => topic.id === query.topicId)) throw new NotFoundError("Topic", query.topicId);
|
|
361
|
-
const page = await this.feedPage(
|
|
505
|
+
const page = await this.feedPage(actor, [spaceId], query);
|
|
362
506
|
this.logger.debug("space feed read", {
|
|
363
507
|
orgId,
|
|
364
508
|
spaceId,
|
|
@@ -366,9 +510,10 @@ var PostServiceImpl = class {
|
|
|
366
510
|
});
|
|
367
511
|
return page;
|
|
368
512
|
}
|
|
369
|
-
async memberFeed(
|
|
370
|
-
const
|
|
371
|
-
const
|
|
513
|
+
async memberFeed(communityId, query, actor) {
|
|
514
|
+
const orgId = actor.orgId;
|
|
515
|
+
const spaceIds = await this.openSpaceIdsIn(actor, communityId);
|
|
516
|
+
const page = await this.feedPage(actor, spaceIds, query);
|
|
372
517
|
this.logger.debug("member feed read", {
|
|
373
518
|
orgId,
|
|
374
519
|
communityId,
|
|
@@ -377,12 +522,13 @@ var PostServiceImpl = class {
|
|
|
377
522
|
});
|
|
378
523
|
return page;
|
|
379
524
|
}
|
|
380
|
-
async openSpaceIdsIn(
|
|
381
|
-
if (!await this.access.isCommunityMember(
|
|
382
|
-
return this.access.openSpaceIds(
|
|
525
|
+
async openSpaceIdsIn(actor, communityId) {
|
|
526
|
+
if (!await this.access.isCommunityMember(communityId, actor)) throw new NotFoundError("Community", communityId);
|
|
527
|
+
return this.access.openSpaceIds(communityId, actor);
|
|
383
528
|
}
|
|
384
|
-
async listOwn(
|
|
385
|
-
const
|
|
529
|
+
async listOwn(communityId, query, actor) {
|
|
530
|
+
const orgId = actor.orgId;
|
|
531
|
+
const spaceIds = await this.openSpaceIdsIn(actor, communityId);
|
|
386
532
|
const page = await this.repo.listByAuthor(orgId, actor.id, spaceIds, query);
|
|
387
533
|
this.logger.debug("own posts read", {
|
|
388
534
|
orgId,
|
|
@@ -391,14 +537,15 @@ var PostServiceImpl = class {
|
|
|
391
537
|
});
|
|
392
538
|
return {
|
|
393
539
|
...page,
|
|
394
|
-
rows: await this.viewsOf(
|
|
540
|
+
rows: await this.viewsOf(actor, page.rows)
|
|
395
541
|
};
|
|
396
542
|
}
|
|
397
|
-
async listBookmarked(
|
|
398
|
-
const
|
|
543
|
+
async listBookmarked(communityId, query, actor) {
|
|
544
|
+
const orgId = actor.orgId;
|
|
545
|
+
const spaceIds = await this.openSpaceIdsIn(actor, communityId);
|
|
399
546
|
const page = await this.repo.listBookmarked(orgId, actor.id, spaceIds, {
|
|
400
547
|
...query,
|
|
401
|
-
visibleStatuses: this.visibleStatuses(actor)
|
|
548
|
+
visibleStatuses: this.visibleStatuses(actor, communityId)
|
|
402
549
|
});
|
|
403
550
|
this.logger.debug("bookmarked posts read", {
|
|
404
551
|
orgId,
|
|
@@ -407,27 +554,47 @@ var PostServiceImpl = class {
|
|
|
407
554
|
});
|
|
408
555
|
return {
|
|
409
556
|
...page,
|
|
410
|
-
rows: await this.viewsOf(
|
|
557
|
+
rows: await this.viewsOf(actor, page.rows)
|
|
411
558
|
};
|
|
412
559
|
}
|
|
413
|
-
async coverFile(
|
|
414
|
-
const post = await this.repo.find(orgId, postId);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
560
|
+
async coverFile(postId, actor) {
|
|
561
|
+
const post = await this.repo.find(actor.orgId, postId);
|
|
562
|
+
return this.signShown(actor, post, post?.coverAssetId ?? null, "Post cover", postId);
|
|
563
|
+
}
|
|
564
|
+
async assetFile(postId, assetId, actor) {
|
|
565
|
+
const post = await this.repo.find(actor.orgId, postId);
|
|
566
|
+
const [upload] = post !== null && this.bodies.parse(post.body).assetIds.includes(assetId) ? await this.images.memberUploads(actor.orgId, [assetId]) : [];
|
|
567
|
+
return this.signShown(actor, post, upload ? assetId : null, "Post file", assetId, upload && !INLINE_TYPES.test(upload.contentType) ? upload.filename : void 0);
|
|
568
|
+
}
|
|
569
|
+
async signShown(actor, post, assetId, label, id, saveAs) {
|
|
570
|
+
const orgId = actor.orgId;
|
|
571
|
+
if (!post || !assetId || !this.canSee(actor, post)) throw new NotFoundError(label, id);
|
|
572
|
+
const space = await this.content.getSpace(post.spaceId, this.authors.systemActor(orgId));
|
|
573
|
+
if (!space || !await this.access.canOpen(space, actor)) throw new NotFoundError(label, id);
|
|
574
|
+
const url = await this.images.signFile(orgId, assetId, saveAs ? { saveAs } : {});
|
|
575
|
+
if (!url) throw new NotFoundError(label, id);
|
|
576
|
+
return url;
|
|
577
|
+
}
|
|
578
|
+
async getPost(postId, actor) {
|
|
579
|
+
const { post } = await this.loadVisible(actor, postId);
|
|
422
580
|
this.logger.debug("post read", {
|
|
423
|
-
orgId,
|
|
581
|
+
orgId: actor.orgId,
|
|
424
582
|
postId
|
|
425
583
|
});
|
|
426
|
-
return this.viewOf(
|
|
584
|
+
return this.viewOf(actor, post);
|
|
427
585
|
}
|
|
428
|
-
async moderationQueue(
|
|
429
|
-
|
|
430
|
-
|
|
586
|
+
async moderationQueue(communityId, spaceId, actor) {
|
|
587
|
+
const orgId = actor.orgId;
|
|
588
|
+
const space = await this.requireOpen(actor, spaceId, communityId);
|
|
589
|
+
if (!actor.permissions.has("communities:moderate", space.communityId)) {
|
|
590
|
+
this.logger.warn("space queue refused — not a moderator", {
|
|
591
|
+
orgId,
|
|
592
|
+
communityId,
|
|
593
|
+
spaceId,
|
|
594
|
+
actorId: actor.id
|
|
595
|
+
});
|
|
596
|
+
throw new ForbiddenError("You need communities:moderate");
|
|
597
|
+
}
|
|
431
598
|
const [pending, flags, openCommentFlags] = await Promise.all([
|
|
432
599
|
this.repo.listPending(orgId, spaceId, 100),
|
|
433
600
|
this.repo.listOpenFlags(orgId, spaceId, 100),
|
|
@@ -441,122 +608,68 @@ var PostServiceImpl = class {
|
|
|
441
608
|
commentFlags: openCommentFlags.length
|
|
442
609
|
});
|
|
443
610
|
return {
|
|
444
|
-
pending: await this.viewsOf(
|
|
611
|
+
pending: await this.viewsOf(actor, pending),
|
|
445
612
|
flags,
|
|
446
|
-
commentFlags: openCommentFlags
|
|
613
|
+
commentFlags: await this.flaggedCommentViews(actor, space.communityId, openCommentFlags)
|
|
447
614
|
};
|
|
448
615
|
}
|
|
449
|
-
|
|
450
|
-
|
|
616
|
+
async flaggedCommentViews(actor, communityId, flags) {
|
|
617
|
+
if (flags.length === 0) return [];
|
|
618
|
+
const orgId = actor.orgId;
|
|
619
|
+
const authorIds = [...new Set(flags.map((flag) => flag.comment.orgUserId))];
|
|
620
|
+
const [records, permissions] = await Promise.all([this.profiles.authorsOf(orgId, authorIds), this.authors.permissionsOfMany(orgId, authorIds)]);
|
|
621
|
+
return flags.map((flag) => {
|
|
622
|
+
const record = records[flag.comment.orgUserId];
|
|
623
|
+
if (!record) throw new NotFoundError("Member", flag.comment.orgUserId);
|
|
624
|
+
return {
|
|
625
|
+
...flag,
|
|
626
|
+
comment: {
|
|
627
|
+
...flag.comment,
|
|
628
|
+
author: authorView(orgId, record, this.fileUrls, teamBadge(permissions.get(flag.comment.orgUserId), communityId))
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
visible(postId, actor) {
|
|
634
|
+
return this.loadVisible(actor, postId);
|
|
451
635
|
}
|
|
452
636
|
find(orgId, postId) {
|
|
453
637
|
return this.repo.find(orgId, postId);
|
|
454
638
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
hasDue(orgId, now) {
|
|
459
|
-
return this.repo.hasDue(orgId, now);
|
|
460
|
-
}
|
|
461
|
-
async publishDue(orgId, now) {
|
|
462
|
-
let afterId;
|
|
463
|
-
let count = 0;
|
|
464
|
-
let refused = 0;
|
|
465
|
-
for (;;) {
|
|
466
|
-
const due = await this.repo.listDue(orgId, now, DUE_BATCH_SIZE, afterId);
|
|
467
|
-
if (due.length === 0) break;
|
|
468
|
-
const batch = await this.submitDue(orgId, now, due);
|
|
469
|
-
count += batch.count;
|
|
470
|
-
refused += batch.refused;
|
|
471
|
-
if (due.length < DUE_BATCH_SIZE) break;
|
|
472
|
-
afterId = due[due.length - 1].id;
|
|
473
|
-
}
|
|
474
|
-
if (count > 0) this.logger.info("due posts submitted", {
|
|
475
|
-
orgId,
|
|
476
|
-
count,
|
|
477
|
-
refused
|
|
478
|
-
});
|
|
479
|
-
else this.logger.debug("no posts due", { orgId });
|
|
480
|
-
}
|
|
481
|
-
async submitDue(orgId, now, due) {
|
|
482
|
-
const spaces = /* @__PURE__ */ new Map();
|
|
483
|
-
const roles = /* @__PURE__ */ new Map();
|
|
484
|
-
const refused = /* @__PURE__ */ new Set();
|
|
485
|
-
for (const post of due) {
|
|
486
|
-
if (!spaces.has(post.spaceId)) spaces.set(post.spaceId, await this.content.getSpace(orgId, post.spaceId));
|
|
487
|
-
if (!roles.has(post.orgUserId)) roles.set(post.orgUserId, await this.authors.roleOf(orgId, post.orgUserId));
|
|
488
|
-
const space = spaces.get(post.spaceId);
|
|
489
|
-
const role = roles.get(post.orgUserId) ?? null;
|
|
490
|
-
if (space && !await this.submittable(orgId, post, space, role)) refused.add(post.id);
|
|
491
|
-
}
|
|
492
|
-
return {
|
|
493
|
-
count: await this.uow.run(async ({ posts, outbox }) => {
|
|
494
|
-
const events = [];
|
|
495
|
-
for (const post of due) {
|
|
496
|
-
const current = await posts.find(orgId, post.id);
|
|
497
|
-
if (!current || current.status !== "scheduled" || current.scheduledAt === null || current.scheduledAt > now) continue;
|
|
498
|
-
const space = spaces.get(current.spaceId);
|
|
499
|
-
if (!space) {
|
|
500
|
-
this.logger.error("scheduled post has no space", {
|
|
501
|
-
orgId,
|
|
502
|
-
postId: current.id,
|
|
503
|
-
spaceId: current.spaceId
|
|
504
|
-
});
|
|
505
|
-
continue;
|
|
506
|
-
}
|
|
507
|
-
const next = await posts.update(orgId, current.id, refused.has(current.id) ? {
|
|
508
|
-
status: "draft",
|
|
509
|
-
scheduledAt: null,
|
|
510
|
-
publishedAt: null,
|
|
511
|
-
removedBy: null
|
|
512
|
-
} : this.submissionOf(space.settings.requireReview, roles.get(current.orgUserId) ?? null, now));
|
|
513
|
-
if (!next) continue;
|
|
514
|
-
const metadata = this.meta(null, current.status);
|
|
515
|
-
events.push(next.status === "published" ? discussionEvents.postPublished.make({
|
|
516
|
-
orgId,
|
|
517
|
-
data: next,
|
|
518
|
-
metadata
|
|
519
|
-
}) : discussionEvents.postUpdated.make({
|
|
520
|
-
orgId,
|
|
521
|
-
data: next,
|
|
522
|
-
metadata
|
|
523
|
-
}));
|
|
524
|
-
}
|
|
525
|
-
if (events.length > 0) await outbox.append(events);
|
|
526
|
-
return events.length;
|
|
527
|
-
}),
|
|
528
|
-
refused: refused.size
|
|
529
|
-
};
|
|
639
|
+
async submittable(post, space, author) {
|
|
640
|
+
if (!this.topicFits(space, post.topicId)) return false;
|
|
641
|
+
return this.access.canOpen(space, author);
|
|
530
642
|
}
|
|
531
|
-
async
|
|
532
|
-
|
|
533
|
-
return this.access.canOpen(orgId, {
|
|
534
|
-
id: post.orgUserId,
|
|
535
|
-
role
|
|
536
|
-
}, space);
|
|
537
|
-
}
|
|
538
|
-
async feedPage(orgId, actor, spaceIds, query) {
|
|
539
|
-
const page = await this.repo.listPublished(orgId, spaceIds, query);
|
|
643
|
+
async feedPage(actor, spaceIds, query) {
|
|
644
|
+
const page = await this.repo.listPublished(actor.orgId, spaceIds, query);
|
|
540
645
|
return {
|
|
541
646
|
...page,
|
|
542
|
-
rows: await this.viewsOf(
|
|
647
|
+
rows: await this.viewsOf(actor, page.rows)
|
|
543
648
|
};
|
|
544
649
|
}
|
|
545
|
-
async viewOf(
|
|
546
|
-
const [view] = await this.viewsOf(
|
|
650
|
+
async viewOf(actor, post) {
|
|
651
|
+
const [view] = await this.viewsOf(actor, [post]);
|
|
547
652
|
return view;
|
|
548
653
|
}
|
|
549
|
-
async viewsOf(
|
|
654
|
+
async viewsOf(actor, posts) {
|
|
550
655
|
if (posts.length === 0) return [];
|
|
656
|
+
const orgId = actor.orgId;
|
|
551
657
|
const postIds = posts.map((post) => post.id);
|
|
552
|
-
const
|
|
553
|
-
|
|
658
|
+
const authorIds = [...new Set(posts.map((post) => post.orgUserId))];
|
|
659
|
+
const now = /* @__PURE__ */ new Date();
|
|
660
|
+
const bodyAssets = new Map(posts.map((post) => [post.id, this.bodies.parse(post.body).assetIds]));
|
|
661
|
+
const [records, permissions, reactions, commentCounts, subscribed, bookmarked, mentioned, uploads] = await Promise.all([
|
|
662
|
+
this.profiles.authorsOf(orgId, authorIds),
|
|
663
|
+
this.authors.permissionsOfMany(orgId, authorIds),
|
|
554
664
|
this.repo.reactionsOf(orgId, postIds, actor.id),
|
|
555
665
|
this.comments.publishedCommentCounts(orgId, postIds),
|
|
556
|
-
this.
|
|
557
|
-
this.repo.bookmarkedAmong(orgId, postIds, actor.id)
|
|
666
|
+
this.subscriptions.forViewer(orgId, actor.id, "post", postIds),
|
|
667
|
+
this.repo.bookmarkedAmong(orgId, postIds, actor.id),
|
|
668
|
+
mentionViews(this.mentions, this.profiles, orgId, "post", postIds),
|
|
669
|
+
this.images.memberUploads(orgId, [...new Set([...bodyAssets.values()].flat())])
|
|
558
670
|
]);
|
|
559
|
-
const
|
|
671
|
+
const uploadOf = new Map(uploads.map((upload) => [upload.id, upload]));
|
|
672
|
+
const subscriptionOf = new Map(subscribed.map((row) => [row.itemId, row]));
|
|
560
673
|
const saved = new Set(bookmarked);
|
|
561
674
|
return posts.map((post) => {
|
|
562
675
|
const record = records[post.orgUserId];
|
|
@@ -564,45 +677,44 @@ var PostServiceImpl = class {
|
|
|
564
677
|
const counts = reactions[post.id];
|
|
565
678
|
return {
|
|
566
679
|
...post,
|
|
567
|
-
coverUrl: post.coverAssetId ? this.fileUrls.
|
|
568
|
-
|
|
680
|
+
coverUrl: !post.coverAssetId ? null : post.status === "published" ? this.fileUrls.publicImage(orgId, post.id, post.coverAssetId) : this.fileUrls.postCover(post.id),
|
|
681
|
+
assets: Object.fromEntries((bodyAssets.get(post.id) ?? []).flatMap((assetId) => {
|
|
682
|
+
const upload = uploadOf.get(assetId);
|
|
683
|
+
return upload ? [[assetId, {
|
|
684
|
+
url: this.fileUrls.postAsset(post.id, assetId),
|
|
685
|
+
kind: upload.kind,
|
|
686
|
+
filename: upload.filename,
|
|
687
|
+
size: upload.size
|
|
688
|
+
}]] : [];
|
|
689
|
+
})),
|
|
690
|
+
author: authorView(orgId, record, this.fileUrls, teamBadge(permissions.get(post.orgUserId), post.communityId)),
|
|
569
691
|
reactions: counts?.reactions ?? {},
|
|
570
692
|
viewerReaction: counts?.viewerReaction ?? null,
|
|
571
693
|
commentCount: commentCounts[post.id] ?? 0,
|
|
572
|
-
|
|
573
|
-
bookmarked: saved.has(post.id)
|
|
694
|
+
subscription: activeSubscription(subscriptionOf.get(post.id), now),
|
|
695
|
+
bookmarked: saved.has(post.id),
|
|
696
|
+
mentions: mentioned[post.id] ?? []
|
|
574
697
|
};
|
|
575
698
|
});
|
|
576
699
|
}
|
|
577
|
-
async commitSubmission(orgId, post, space,
|
|
700
|
+
async commitSubmission(orgId, post, space, skips, actor, now) {
|
|
701
|
+
const mentioned = await this.mentionedOpeners(orgId, post);
|
|
578
702
|
return this.uow.run(async ({ posts, outbox }) => {
|
|
579
|
-
const next = await
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
orgId,
|
|
583
|
-
data: next,
|
|
584
|
-
metadata
|
|
585
|
-
}) : discussionEvents.postUpdated.make({
|
|
586
|
-
orgId,
|
|
587
|
-
data: next,
|
|
588
|
-
metadata
|
|
589
|
-
});
|
|
590
|
-
await outbox.append([event]);
|
|
703
|
+
const next = await posts.updateIf(orgId, post.id, { status: post.status }, this.submissionOf(space.settings.requireReview, skips, now));
|
|
704
|
+
if (!next) throw new ConflictError("This post cannot be submitted");
|
|
705
|
+
await outbox.append([this.landed(orgId, next, this.meta(actor, post.status), mentioned)]);
|
|
591
706
|
return next;
|
|
592
707
|
});
|
|
593
708
|
}
|
|
594
|
-
async moderate(
|
|
595
|
-
const
|
|
596
|
-
|
|
709
|
+
async moderate(postId, actor, patch, unchanged, log) {
|
|
710
|
+
const orgId = actor.orgId;
|
|
711
|
+
const { post } = await this.loadVisible(actor, postId);
|
|
712
|
+
this.requireModerator(actor, post, log);
|
|
597
713
|
if (post.status === "removed") throw new ConflictError("This post was removed");
|
|
598
|
-
if (unchanged(post)) return this.viewOf(
|
|
714
|
+
if (unchanged(post)) return this.viewOf(actor, post);
|
|
599
715
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
600
716
|
const next = await this.mustUpdate(posts, orgId, postId, patch);
|
|
601
|
-
await outbox.append([
|
|
602
|
-
orgId,
|
|
603
|
-
data: next,
|
|
604
|
-
metadata: this.meta(actor, next.status === "pending" ? post.status : void 0)
|
|
605
|
-
})]);
|
|
717
|
+
await outbox.append([this.updated(orgId, next, this.meta(actor, next.status === "pending" ? post.status : void 0))]);
|
|
606
718
|
return next;
|
|
607
719
|
});
|
|
608
720
|
this.logger.info(log, {
|
|
@@ -610,7 +722,7 @@ var PostServiceImpl = class {
|
|
|
610
722
|
postId,
|
|
611
723
|
actorId: actor.id
|
|
612
724
|
});
|
|
613
|
-
return this.viewOf(
|
|
725
|
+
return this.viewOf(actor, updated);
|
|
614
726
|
}
|
|
615
727
|
async mustUpdate(posts, orgId, postId, patch) {
|
|
616
728
|
const next = await posts.update(orgId, postId, patch);
|
|
@@ -634,10 +746,10 @@ var PostServiceImpl = class {
|
|
|
634
746
|
removedBy: null
|
|
635
747
|
};
|
|
636
748
|
}
|
|
637
|
-
return this.submissionOf(space.settings.requireReview, actor.
|
|
749
|
+
return this.submissionOf(space.settings.requireReview, skipsReview(actor.permissions, { communityId: space.communityId }), now);
|
|
638
750
|
}
|
|
639
|
-
submissionOf(requireReview,
|
|
640
|
-
if (requireReview && !
|
|
751
|
+
submissionOf(requireReview, skips, now) {
|
|
752
|
+
if (requireReview && !skips) return {
|
|
641
753
|
status: "pending",
|
|
642
754
|
scheduledAt: null,
|
|
643
755
|
publishedAt: null,
|
|
@@ -661,20 +773,31 @@ var PostServiceImpl = class {
|
|
|
661
773
|
throw new NotFoundError("Asset", assetId);
|
|
662
774
|
}
|
|
663
775
|
}
|
|
664
|
-
async
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
776
|
+
async requireBodyAssets(orgId, orgUserId, assetIds) {
|
|
777
|
+
if (assetIds.length > MAX_BODY_ASSETS) throw new InvalidInputError(`A post shows at most ${MAX_BODY_ASSETS} files`);
|
|
778
|
+
for (const assetId of assetIds) if (!await this.images.ownsUpload(orgId, assetId, orgUserId)) {
|
|
779
|
+
this.logger.debug("body file refused", {
|
|
780
|
+
orgId,
|
|
781
|
+
orgUserId,
|
|
782
|
+
assetId
|
|
783
|
+
});
|
|
784
|
+
throw new NotFoundError("Asset", assetId);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
async openSpace(actor, spaceId, communityId) {
|
|
788
|
+
const space = await this.content.getSpace(spaceId, this.authors.systemActor(actor.orgId));
|
|
789
|
+
if (!space || communityId != null && space.communityId !== communityId) return null;
|
|
790
|
+
return await this.access.canOpen(space, actor) ? space : null;
|
|
668
791
|
}
|
|
669
|
-
async requireOpen(
|
|
670
|
-
const space = await this.openSpace(
|
|
792
|
+
async requireOpen(actor, spaceId, communityId) {
|
|
793
|
+
const space = await this.openSpace(actor, spaceId, communityId);
|
|
671
794
|
if (!space) throw new NotFoundError("Space", spaceId);
|
|
672
795
|
return space;
|
|
673
796
|
}
|
|
674
|
-
async loadVisible(
|
|
675
|
-
const post = await this.repo.find(orgId, postId);
|
|
676
|
-
if (!post || communityId
|
|
677
|
-
const space = await this.openSpace(
|
|
797
|
+
async loadVisible(actor, postId, communityId) {
|
|
798
|
+
const post = await this.repo.find(actor.orgId, postId);
|
|
799
|
+
if (!post || communityId != null && post.communityId !== communityId) throw new NotFoundError("Post", postId);
|
|
800
|
+
const space = await this.openSpace(actor, post.spaceId);
|
|
678
801
|
if (!space || !this.canSee(actor, post)) throw new NotFoundError("Post", postId);
|
|
679
802
|
return {
|
|
680
803
|
post,
|
|
@@ -682,25 +805,34 @@ var PostServiceImpl = class {
|
|
|
682
805
|
};
|
|
683
806
|
}
|
|
684
807
|
canSee(actor, post) {
|
|
685
|
-
return post.orgUserId === actor.id || this.visibleStatuses(actor).includes(post.status);
|
|
686
|
-
}
|
|
687
|
-
visibleStatuses(actor) {
|
|
688
|
-
return this.
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
return
|
|
808
|
+
return post.orgUserId === actor.id || this.visibleStatuses(actor, post.communityId).includes(post.status);
|
|
809
|
+
}
|
|
810
|
+
visibleStatuses(actor, communityId) {
|
|
811
|
+
return this.moderates(actor.permissions, communityId) ? MODERATOR_VISIBLE : PUBLISHED_ONLY;
|
|
812
|
+
}
|
|
813
|
+
moderates(permissions, communityId) {
|
|
814
|
+
return permissions.has("communities:moderate", communityId);
|
|
815
|
+
}
|
|
816
|
+
requireModerator(actor, post, action) {
|
|
817
|
+
if (!this.moderates(actor.permissions, post.communityId)) {
|
|
818
|
+
this.logger.warn("post moderation refused — not a moderator", {
|
|
819
|
+
orgId: actor.orgId,
|
|
820
|
+
postId: post.id,
|
|
821
|
+
communityId: post.communityId,
|
|
822
|
+
action,
|
|
823
|
+
actorId: actor.id
|
|
824
|
+
});
|
|
825
|
+
throw new ForbiddenError("You need communities:moderate");
|
|
826
|
+
}
|
|
692
827
|
}
|
|
693
828
|
assertAuthor(actor, post) {
|
|
694
829
|
if (post.orgUserId !== actor.id) throw new ForbiddenError("You can change only your own post");
|
|
695
830
|
}
|
|
696
831
|
topicFits(space, topicId) {
|
|
697
|
-
|
|
698
|
-
return space.topics.some((topic) => topic.id === topicId);
|
|
832
|
+
return topicId === null || space.topics.some((topic) => topic.id === topicId);
|
|
699
833
|
}
|
|
700
834
|
assertTopic(space, topicId) {
|
|
701
|
-
if (this.topicFits(space, topicId))
|
|
702
|
-
if (topicId === null) throw new InvalidInputError("This space requires a topic");
|
|
703
|
-
throw new NotFoundError("Topic", topicId);
|
|
835
|
+
if (topicId !== null && !this.topicFits(space, topicId)) throw new NotFoundError("Topic", topicId);
|
|
704
836
|
}
|
|
705
837
|
patchChanges(post, patch) {
|
|
706
838
|
return Object.keys(patch).some((key) => {
|
|
@@ -710,9 +842,43 @@ var PostServiceImpl = class {
|
|
|
710
842
|
return post[key] !== value;
|
|
711
843
|
});
|
|
712
844
|
}
|
|
845
|
+
async mentionable(orgId, spaceId, title, body) {
|
|
846
|
+
return this.openersOf(orgId, spaceId, [.../* @__PURE__ */ new Set([...mentionTokens(title), ...body])]);
|
|
847
|
+
}
|
|
848
|
+
async openersOf(orgId, spaceId, orgUserIds) {
|
|
849
|
+
if (orgUserIds.length === 0) return [];
|
|
850
|
+
return keepAllowed(orgUserIds, await this.access.spaceOpenersAmong(orgId, spaceId, orgUserIds));
|
|
851
|
+
}
|
|
852
|
+
async mentionedOpeners(orgId, post) {
|
|
853
|
+
return this.openersOf(orgId, post.spaceId, await this.mentions.ofItem(orgId, "post", post.id));
|
|
854
|
+
}
|
|
855
|
+
landed(orgId, post, metadata, mentioned) {
|
|
856
|
+
if (post.status !== "published") return this.updated(orgId, post, metadata);
|
|
857
|
+
return this.published(orgId, post, metadata, mentioned);
|
|
858
|
+
}
|
|
859
|
+
published(orgId, post, metadata, mentionedOrgUserIds) {
|
|
860
|
+
return discussionEvents.postPublished.make({
|
|
861
|
+
orgId,
|
|
862
|
+
data: {
|
|
863
|
+
...post,
|
|
864
|
+
mentionedOrgUserIds
|
|
865
|
+
},
|
|
866
|
+
metadata
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
updated(orgId, post, metadata, mentionedOrgUserIds = []) {
|
|
870
|
+
return discussionEvents.postUpdated.make({
|
|
871
|
+
orgId,
|
|
872
|
+
data: {
|
|
873
|
+
...post,
|
|
874
|
+
mentionedOrgUserIds
|
|
875
|
+
},
|
|
876
|
+
metadata
|
|
877
|
+
});
|
|
878
|
+
}
|
|
713
879
|
meta(actor, previousStatus) {
|
|
714
880
|
return {
|
|
715
|
-
...actor ? {
|
|
881
|
+
...actor ? { actorId: actor.id } : {},
|
|
716
882
|
...previousStatus !== void 0 ? { previousStatus } : {}
|
|
717
883
|
};
|
|
718
884
|
}
|