@headless-lms/core 0.4.0 → 0.6.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 +15 -9
- package/dist/assets/file-urls.d.ts +14 -7
- package/dist/assets/file-urls.js +15 -4
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +4 -4
- package/dist/assets/index.js +3 -3
- package/dist/assets/ports.d.ts +34 -15
- package/dist/assets/service.d.ts +30 -14
- package/dist/assets/service.js +233 -54
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.js +68 -3
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +16 -16
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +34 -4
- package/dist/automations/community-mail.js +32 -4
- 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/index.d.ts +2 -1
- package/dist/automations/index.js +2 -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.js +1 -1
- package/dist/automations/notifications/debounce.js.map +1 -1
- package/dist/automations/notifications/defaults.js +33 -3
- package/dist/automations/notifications/defaults.js.map +1 -1
- package/dist/automations/notifications/processor.js +1 -1
- package/dist/automations/notifications/processor.js.map +1 -1
- package/dist/automations/notifications/rules/content.js +60 -3
- package/dist/automations/notifications/rules/content.js.map +1 -1
- package/dist/automations/notifications/rules/discussion.js +29 -29
- package/dist/automations/notifications/rules/discussion.js.map +1 -1
- package/dist/automations/notifications/rules/entitlements.js +36 -9
- package/dist/automations/notifications/rules/entitlements.js.map +1 -1
- 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/organizations.js +17 -10
- package/dist/automations/notifications/rules/organizations.js.map +1 -1
- package/dist/automations/notifications/rules/recipients.js +0 -1
- package/dist/automations/notifications/rules/recipients.js.map +1 -1
- package/dist/automations/notifications/service.d.ts +8 -6
- package/dist/automations/notifications/service.js +12 -12
- package/dist/automations/notifications/service.js.map +1 -1
- package/dist/automations/notifications/types.js +94 -7
- package/dist/automations/notifications/types.js.map +1 -1
- package/dist/automations/ports.d.ts +30 -31
- package/dist/automations/service.d.ts +20 -17
- package/dist/automations/service.js +90 -61
- 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 +465 -88
- package/dist/content/events.js +34 -1
- 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/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 +5 -4
- package/dist/content/notification-categories.js +15 -6
- package/dist/content/notification-categories.js.map +1 -1
- package/dist/content/ports.d.ts +113 -68
- package/dist/content/service.d.ts +103 -67
- package/dist/content/service.js +739 -152
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +6 -3
- package/dist/content/titles.js +37 -24
- package/dist/content/titles.js.map +1 -1
- 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 +56 -37
- package/dist/discussion/chats.js +392 -160
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/events.d.ts +85 -47
- package/dist/discussion/events.js +41 -3
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +8 -5
- package/dist/discussion/index.js +5 -3
- 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.js +2 -2
- package/dist/discussion/mentions.js.map +1 -1
- package/dist/discussion/ports.d.ts +182 -100
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +45 -39
- package/dist/discussion/posts.js +266 -152
- 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 +37 -31
- package/dist/discussion/service.js +250 -186
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +8 -8
- package/dist/discussion/subscriptions.js +28 -12
- package/dist/discussion/subscriptions.js.map +1 -1
- package/dist/discussion/titles.d.ts +4 -2
- package/dist/discussion/titles.js +27 -13
- package/dist/discussion/titles.js.map +1 -1
- package/dist/entitlements/events.d.ts +58 -58
- package/dist/entitlements/index.d.ts +4 -4
- package/dist/entitlements/index.js +2 -2
- package/dist/entitlements/model.d.ts +2 -2
- package/dist/entitlements/ports.d.ts +65 -39
- 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 +40 -28
- package/dist/entitlements/service.js +369 -162
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +3 -3
- package/dist/identity/devices.js +6 -4
- package/dist/identity/devices.js.map +1 -1
- package/dist/identity/index.d.ts +2 -2
- package/dist/identity/index.js +2 -2
- package/dist/identity/notification-settings.d.ts +1 -1
- package/dist/identity/notification-settings.js +2 -1
- package/dist/identity/notification-settings.js.map +1 -1
- package/dist/identity/ports.d.ts +7 -7
- package/dist/identity/service.d.ts +8 -7
- package/dist/identity/service.js +16 -12
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/events.d.ts +3 -3
- package/dist/integrations/ports.d.ts +9 -8
- package/dist/integrations/service.d.ts +9 -8
- package/dist/integrations/service.js +31 -14
- 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 +47 -0
- package/dist/organizations/effective-permissions.js +118 -0
- package/dist/organizations/effective-permissions.js.map +1 -0
- package/dist/organizations/events.d.ts +326 -181
- package/dist/organizations/events.js +90 -18
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +14 -10
- package/dist/organizations/index.js +7 -3
- package/dist/organizations/members.d.ts +66 -8
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/model.d.ts +2 -2
- 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 +6 -4
- package/dist/organizations/notification-categories.js +25 -10
- package/dist/organizations/notification-categories.js.map +1 -1
- 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 +102 -35
- package/dist/organizations/service.js +1254 -327
- 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/events.d.ts +14 -14
- 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 +21 -6
- 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 +29 -5
- package/dist/reporting/member/ports.d.ts +49 -29
- package/dist/reporting/member/service.d.ts +57 -29
- package/dist/reporting/member/service.js +345 -114
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +23 -3
- package/dist/shared/errors.d.ts +2 -1
- package/dist/shared/errors.js +3 -1
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +4 -0
- package/dist/shared/id.js +4 -0
- 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/ports.d.ts +25 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +4 -5
- package/dist/types/email-templates.d.ts +22 -3
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +13 -16
- package/dist/types/index.d.ts +9 -9
- package/dist/types/index.js +2 -1
- package/dist/types/notifications.d.ts +4 -4
- 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/content.d.ts +599 -13
- package/dist/types/schemas/content.js +219 -8
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +280 -57
- package/dist/types/schemas/discussion.js +77 -3
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +21 -8
- package/dist/types/schemas/entitlements.js +11 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/index.d.ts +5 -5
- package/dist/types/schemas/index.js +5 -5
- package/dist/types/schemas/notifications.d.ts +11 -10
- package/dist/types/schemas/notifications.js +3 -2
- package/dist/types/schemas/notifications.js.map +1 -1
- package/dist/types/schemas/organizations.d.ts +99 -60
- package/dist/types/schemas/organizations.js +80 -27
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +3 -4
- package/dist/types/schemas/progress.js +0 -1
- package/dist/types/schemas/progress.js.map +1 -1
- package/package.json +1 -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,14 +1,16 @@
|
|
|
1
1
|
import { genId } from "../shared/id.js";
|
|
2
2
|
import { ConflictError, ForbiddenError, InvalidInputError, NotFoundError, UnavailableError } from "../shared/errors.js";
|
|
3
3
|
import { noopLogger } from "../shared/logger.js";
|
|
4
|
-
import { canModerate, isStaffRole } from "../organizations/roles.js";
|
|
5
4
|
import { discussionEvents } from "./events.js";
|
|
6
5
|
import { authorView } from "./authors.js";
|
|
7
6
|
import { keepAllowed, mentionTokens, mentionViews } from "./mentions.js";
|
|
7
|
+
import { skipsReview, teamBadge } from "./rules.js";
|
|
8
8
|
import { POST_PUBLISH_JOB } from "./jobs.js";
|
|
9
9
|
import { activeSubscription } from "./subscriptions.js";
|
|
10
10
|
import "./ports.js";
|
|
11
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;
|
|
12
14
|
const PUBLISHED_ONLY = ["published"];
|
|
13
15
|
const MODERATOR_VISIBLE = [
|
|
14
16
|
"published",
|
|
@@ -32,6 +34,7 @@ var PostServiceImpl = class {
|
|
|
32
34
|
mentions;
|
|
33
35
|
images;
|
|
34
36
|
fileUrls;
|
|
37
|
+
bodies;
|
|
35
38
|
uow;
|
|
36
39
|
scheduler;
|
|
37
40
|
logger;
|
|
@@ -46,22 +49,27 @@ var PostServiceImpl = class {
|
|
|
46
49
|
this.mentions = params.mentions;
|
|
47
50
|
this.images = params.images;
|
|
48
51
|
this.fileUrls = params.fileUrls;
|
|
52
|
+
this.bodies = params.bodies;
|
|
49
53
|
this.uow = params.uow;
|
|
50
54
|
this.scheduler = params.scheduler;
|
|
51
55
|
this.logger = params.logger ?? noopLogger;
|
|
52
56
|
}
|
|
53
|
-
async visibleTitles(
|
|
57
|
+
async visibleTitles(postIds, actor) {
|
|
58
|
+
const orgId = actor.orgId;
|
|
54
59
|
const found = await this.repo.findMany(orgId, [...new Set(postIds)]);
|
|
55
60
|
const communityIds = [...new Set(found.map((post) => post.communityId))];
|
|
56
|
-
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());
|
|
57
62
|
return Object.fromEntries(found.filter((post) => open.has(post.spaceId) && this.canSee(actor, post)).map((post) => [post.id, post.title]));
|
|
58
63
|
}
|
|
59
64
|
publishedBetween(orgId, communityId, from, to) {
|
|
60
65
|
return this.repo.publishedBetween(orgId, communityId, from, to);
|
|
61
66
|
}
|
|
62
|
-
async create(
|
|
63
|
-
const
|
|
67
|
+
async create(input, actor) {
|
|
68
|
+
const orgId = actor.orgId;
|
|
69
|
+
const space = await this.requireOpen(actor, input.spaceId, input.communityId);
|
|
64
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);
|
|
65
73
|
this.assertTopic(space, input.topicId);
|
|
66
74
|
const now = /* @__PURE__ */ new Date();
|
|
67
75
|
const landing = this.initial(input, space, actor, now);
|
|
@@ -83,7 +91,7 @@ var PostServiceImpl = class {
|
|
|
83
91
|
updatedAt: now,
|
|
84
92
|
...landing
|
|
85
93
|
};
|
|
86
|
-
const mentioned = await this.mentionable(orgId, space.id, post.title,
|
|
94
|
+
const mentioned = await this.mentionable(orgId, space.id, post.title, parsed.mentions);
|
|
87
95
|
const saved = await this.uow.run(async ({ posts, mentions, outbox }) => {
|
|
88
96
|
const created = await posts.insert(orgId, post);
|
|
89
97
|
await mentions.sync(orgId, "post", created.id, mentioned, now);
|
|
@@ -103,17 +111,21 @@ var PostServiceImpl = class {
|
|
|
103
111
|
actorId: actor.id
|
|
104
112
|
});
|
|
105
113
|
if (saved.status === "scheduled") await this.arm(orgId, actor, saved, null);
|
|
106
|
-
|
|
114
|
+
await this.syncCover(saved);
|
|
115
|
+
return this.viewOf(actor, saved);
|
|
107
116
|
}
|
|
108
|
-
async update(
|
|
109
|
-
const
|
|
117
|
+
async update(postId, patch, actor) {
|
|
118
|
+
const orgId = actor.orgId;
|
|
119
|
+
const { post, space } = await this.loadVisible(actor, postId);
|
|
110
120
|
this.assertAuthor(actor, post);
|
|
111
121
|
if (post.status === "removed") throw new ConflictError("This post was removed");
|
|
112
122
|
if (patch.topicId !== void 0) this.assertTopic(space, patch.topicId);
|
|
113
123
|
if (typeof patch.coverAssetId === "string") await this.requireOwnedImage(orgId, actor.id, patch.coverAssetId);
|
|
114
|
-
|
|
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);
|
|
115
127
|
const retext = patch.title !== void 0 || patch.body !== void 0;
|
|
116
|
-
const candidates = retext ? await this.mentionable(orgId, post.spaceId, patch.title ?? post.title,
|
|
128
|
+
const candidates = retext ? await this.mentionable(orgId, post.spaceId, patch.title ?? post.title, parsed.mentions) : [];
|
|
117
129
|
const updated = await this.uow.run(async ({ posts, mentions, outbox }) => {
|
|
118
130
|
const next = await this.mustUpdate(posts, orgId, postId, patch);
|
|
119
131
|
const added = retext ? await mentions.sync(orgId, "post", postId, candidates, /* @__PURE__ */ new Date()) : [];
|
|
@@ -126,14 +138,16 @@ var PostServiceImpl = class {
|
|
|
126
138
|
fields: Object.keys(patch),
|
|
127
139
|
actorId: actor.id
|
|
128
140
|
});
|
|
129
|
-
|
|
141
|
+
await this.syncCover(updated);
|
|
142
|
+
return this.viewOf(actor, updated);
|
|
130
143
|
}
|
|
131
|
-
async submit(
|
|
132
|
-
const
|
|
144
|
+
async submit(postId, actor) {
|
|
145
|
+
const orgId = actor.orgId;
|
|
146
|
+
const { post, space } = await this.loadVisible(actor, postId);
|
|
133
147
|
this.assertAuthor(actor, post);
|
|
134
148
|
if (post.status !== "draft" && post.status !== "scheduled") throw new ConflictError("This post cannot be submitted");
|
|
135
149
|
this.assertTopic(space, post.topicId);
|
|
136
|
-
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());
|
|
137
151
|
this.logger.info("post submitted", {
|
|
138
152
|
orgId,
|
|
139
153
|
postId,
|
|
@@ -141,10 +155,12 @@ var PostServiceImpl = class {
|
|
|
141
155
|
actorId: actor.id
|
|
142
156
|
});
|
|
143
157
|
if (post.status === "scheduled") await this.disarm(orgId, postId);
|
|
144
|
-
|
|
158
|
+
await this.syncCover(saved);
|
|
159
|
+
return this.viewOf(actor, saved);
|
|
145
160
|
}
|
|
146
|
-
async schedule(
|
|
147
|
-
const
|
|
161
|
+
async schedule(postId, scheduledAt, actor) {
|
|
162
|
+
const orgId = actor.orgId;
|
|
163
|
+
const { post } = await this.loadVisible(actor, postId);
|
|
148
164
|
this.assertAuthor(actor, post);
|
|
149
165
|
if (post.status === "published" || post.status === "removed" || post.status === "pending") throw new ConflictError("This post cannot be scheduled");
|
|
150
166
|
if (scheduledAt <= /* @__PURE__ */ new Date()) throw new InvalidInputError("A scheduled time must be in the future");
|
|
@@ -166,10 +182,12 @@ var PostServiceImpl = class {
|
|
|
166
182
|
actorId: actor.id
|
|
167
183
|
});
|
|
168
184
|
await this.arm(orgId, actor, updated, post.status === "scheduled" ? post.scheduledAt : null);
|
|
169
|
-
|
|
185
|
+
await this.syncCover(updated);
|
|
186
|
+
return this.viewOf(actor, updated);
|
|
170
187
|
}
|
|
171
|
-
async unschedule(
|
|
172
|
-
const
|
|
188
|
+
async unschedule(postId, actor) {
|
|
189
|
+
const orgId = actor.orgId;
|
|
190
|
+
const { post } = await this.loadVisible(actor, postId);
|
|
173
191
|
this.assertAuthor(actor, post);
|
|
174
192
|
if (post.status !== "scheduled") throw new ConflictError("This post is not scheduled");
|
|
175
193
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
@@ -184,7 +202,8 @@ var PostServiceImpl = class {
|
|
|
184
202
|
actorId: actor.id
|
|
185
203
|
});
|
|
186
204
|
await this.disarm(orgId, postId);
|
|
187
|
-
|
|
205
|
+
await this.syncCover(updated);
|
|
206
|
+
return this.viewOf(actor, updated);
|
|
188
207
|
}
|
|
189
208
|
async publishScheduled(orgId, postId, now) {
|
|
190
209
|
const post = await this.repo.find(orgId, postId);
|
|
@@ -207,7 +226,7 @@ var PostServiceImpl = class {
|
|
|
207
226
|
});
|
|
208
227
|
return;
|
|
209
228
|
}
|
|
210
|
-
const space = await this.content.getSpace(
|
|
229
|
+
const space = await this.content.getSpace(post.spaceId, this.authors.systemActor(orgId));
|
|
211
230
|
if (!space) {
|
|
212
231
|
this.logger.error("scheduled post has no space", {
|
|
213
232
|
orgId,
|
|
@@ -216,16 +235,17 @@ var PostServiceImpl = class {
|
|
|
216
235
|
});
|
|
217
236
|
return;
|
|
218
237
|
}
|
|
219
|
-
const
|
|
220
|
-
const refused = !await this.submittable(
|
|
221
|
-
const patch = refused ? DRAFT : this.submissionOf(space.settings.requireReview,
|
|
222
|
-
const
|
|
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 }) => {
|
|
223
243
|
const next = await posts.updateIf(orgId, postId, {
|
|
224
244
|
status: "scheduled",
|
|
225
245
|
dueBy: now
|
|
226
246
|
}, patch);
|
|
227
247
|
if (!next) return null;
|
|
228
|
-
await outbox.append([
|
|
248
|
+
await outbox.append([this.landed(orgId, next, this.meta(null, post.status), mentioned)]);
|
|
229
249
|
return next;
|
|
230
250
|
});
|
|
231
251
|
if (!saved) {
|
|
@@ -241,6 +261,7 @@ var PostServiceImpl = class {
|
|
|
241
261
|
status: saved.status,
|
|
242
262
|
refused
|
|
243
263
|
});
|
|
264
|
+
await this.syncCover(saved);
|
|
244
265
|
}
|
|
245
266
|
async arm(orgId, actor, post, previousScheduledAt) {
|
|
246
267
|
if (post.scheduledAt === null) return;
|
|
@@ -267,6 +288,9 @@ var PostServiceImpl = class {
|
|
|
267
288
|
});
|
|
268
289
|
throw new UnavailableError("The post could not be scheduled; try again", { postId: post.id });
|
|
269
290
|
}
|
|
291
|
+
syncCover(post) {
|
|
292
|
+
return this.images.syncPublicImages(post.orgId, post.id, post.status === "published" && post.coverAssetId ? [post.coverAssetId] : []);
|
|
293
|
+
}
|
|
270
294
|
async disarm(orgId, postId) {
|
|
271
295
|
try {
|
|
272
296
|
await this.scheduler.cancel(POST_PUBLISH_JOB, postId);
|
|
@@ -278,19 +302,21 @@ var PostServiceImpl = class {
|
|
|
278
302
|
});
|
|
279
303
|
}
|
|
280
304
|
}
|
|
281
|
-
async approve(
|
|
282
|
-
const
|
|
283
|
-
|
|
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");
|
|
284
309
|
if (post.status !== "pending") throw new ConflictError("This post is not awaiting review");
|
|
285
310
|
const now = /* @__PURE__ */ new Date();
|
|
286
|
-
const
|
|
311
|
+
const mentioned = await this.mentionedOpeners(orgId, post);
|
|
312
|
+
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
287
313
|
const next = await this.mustUpdate(posts, orgId, postId, {
|
|
288
314
|
status: "published",
|
|
289
315
|
publishedAt: now,
|
|
290
316
|
scheduledAt: null,
|
|
291
317
|
removedBy: null
|
|
292
318
|
});
|
|
293
|
-
await outbox.append([
|
|
319
|
+
await outbox.append([this.landed(orgId, next, this.meta(actor, post.status), mentioned)]);
|
|
294
320
|
return next;
|
|
295
321
|
});
|
|
296
322
|
this.logger.info("post approved", {
|
|
@@ -298,11 +324,13 @@ var PostServiceImpl = class {
|
|
|
298
324
|
postId,
|
|
299
325
|
actorId: actor.id
|
|
300
326
|
});
|
|
301
|
-
|
|
327
|
+
await this.syncCover(updated);
|
|
328
|
+
return this.viewOf(actor, updated);
|
|
302
329
|
}
|
|
303
|
-
async remove(
|
|
304
|
-
const
|
|
305
|
-
|
|
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");
|
|
306
334
|
if (post.status === "removed") throw new ConflictError("This post was removed");
|
|
307
335
|
if (post.status !== "published" && post.status !== "pending") throw new ConflictError("This post cannot be removed");
|
|
308
336
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
@@ -327,16 +355,19 @@ var PostServiceImpl = class {
|
|
|
327
355
|
postId,
|
|
328
356
|
actorId: actor.id
|
|
329
357
|
});
|
|
330
|
-
|
|
358
|
+
await this.syncCover(updated);
|
|
359
|
+
return this.viewOf(actor, updated);
|
|
331
360
|
}
|
|
332
|
-
async restore(
|
|
333
|
-
const
|
|
334
|
-
|
|
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");
|
|
335
365
|
if (post.status !== "removed") throw new ConflictError("This post was not removed");
|
|
336
366
|
const now = /* @__PURE__ */ new Date();
|
|
337
|
-
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 });
|
|
338
369
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
339
|
-
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));
|
|
340
371
|
const metadata = this.meta(actor, post.status);
|
|
341
372
|
await outbox.append([next.status === "published" ? this.published(orgId, next, metadata, []) : this.updated(orgId, next, metadata)]);
|
|
342
373
|
return next;
|
|
@@ -347,25 +378,27 @@ var PostServiceImpl = class {
|
|
|
347
378
|
status: updated.status,
|
|
348
379
|
actorId: actor.id
|
|
349
380
|
});
|
|
350
|
-
|
|
381
|
+
await this.syncCover(updated);
|
|
382
|
+
return this.viewOf(actor, updated);
|
|
351
383
|
}
|
|
352
|
-
pin(
|
|
353
|
-
return this.moderate(
|
|
384
|
+
pin(postId, actor) {
|
|
385
|
+
return this.moderate(postId, actor, { pinned: true }, (post) => post.pinned, "post pinned");
|
|
354
386
|
}
|
|
355
|
-
unpin(
|
|
356
|
-
return this.moderate(
|
|
387
|
+
unpin(postId, actor) {
|
|
388
|
+
return this.moderate(postId, actor, { pinned: false }, (post) => !post.pinned, "post unpinned");
|
|
357
389
|
}
|
|
358
|
-
closeComments(
|
|
359
|
-
return this.moderate(
|
|
390
|
+
closeComments(postId, actor) {
|
|
391
|
+
return this.moderate(postId, actor, { commentsClosed: true }, (post) => post.commentsClosed === true, "post comments closed");
|
|
360
392
|
}
|
|
361
|
-
openComments(
|
|
362
|
-
return this.moderate(
|
|
393
|
+
openComments(postId, actor) {
|
|
394
|
+
return this.moderate(postId, actor, { commentsClosed: false }, (post) => post.commentsClosed === false, "post comments opened");
|
|
363
395
|
}
|
|
364
|
-
setReactions(
|
|
365
|
-
return this.moderate(
|
|
396
|
+
setReactions(postId, enabled, actor) {
|
|
397
|
+
return this.moderate(postId, actor, { reactionsEnabled: enabled }, (post) => post.reactionsEnabled === enabled, "post reactions changed");
|
|
366
398
|
}
|
|
367
|
-
async setReaction(
|
|
368
|
-
const
|
|
399
|
+
async setReaction(postId, emoji, actor) {
|
|
400
|
+
const orgId = actor.orgId;
|
|
401
|
+
const { post, space } = await this.loadVisible(actor, postId);
|
|
369
402
|
if (!(post.reactionsEnabled ?? space.settings.reactions)) throw new ForbiddenError("Reactions are off on this post");
|
|
370
403
|
await this.uow.run(async ({ posts, outbox }) => {
|
|
371
404
|
if (!await posts.setReaction(orgId, postId, actor.id, emoji) || emoji === null) return;
|
|
@@ -388,8 +421,9 @@ var PostServiceImpl = class {
|
|
|
388
421
|
});
|
|
389
422
|
return (await this.repo.reactionsOf(orgId, [postId], actor.id))[postId] ?? { reactions: {} };
|
|
390
423
|
}
|
|
391
|
-
async bookmark(
|
|
392
|
-
|
|
424
|
+
async bookmark(postId, actor) {
|
|
425
|
+
const orgId = actor.orgId;
|
|
426
|
+
await this.loadVisible(actor, postId);
|
|
393
427
|
await this.repo.bookmark(orgId, postId, actor.id);
|
|
394
428
|
this.logger.debug("post bookmarked", {
|
|
395
429
|
orgId,
|
|
@@ -397,8 +431,9 @@ var PostServiceImpl = class {
|
|
|
397
431
|
orgUserId: actor.id
|
|
398
432
|
});
|
|
399
433
|
}
|
|
400
|
-
async unbookmark(
|
|
401
|
-
|
|
434
|
+
async unbookmark(postId, actor) {
|
|
435
|
+
const orgId = actor.orgId;
|
|
436
|
+
await this.loadVisible(actor, postId);
|
|
402
437
|
await this.repo.unbookmark(orgId, postId, actor.id);
|
|
403
438
|
this.logger.debug("post unbookmarked", {
|
|
404
439
|
orgId,
|
|
@@ -406,8 +441,9 @@ var PostServiceImpl = class {
|
|
|
406
441
|
orgUserId: actor.id
|
|
407
442
|
});
|
|
408
443
|
}
|
|
409
|
-
async flag(
|
|
410
|
-
const
|
|
444
|
+
async flag(postId, reason, actor) {
|
|
445
|
+
const orgId = actor.orgId;
|
|
446
|
+
const { post } = await this.loadVisible(actor, postId);
|
|
411
447
|
const now = /* @__PURE__ */ new Date();
|
|
412
448
|
const flag = {
|
|
413
449
|
orgId,
|
|
@@ -446,9 +482,10 @@ var PostServiceImpl = class {
|
|
|
446
482
|
});
|
|
447
483
|
return saved;
|
|
448
484
|
}
|
|
449
|
-
async resolveFlags(
|
|
450
|
-
|
|
451
|
-
|
|
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");
|
|
452
489
|
await this.repo.resolveFlags(orgId, postId, {
|
|
453
490
|
resolvedAt: /* @__PURE__ */ new Date(),
|
|
454
491
|
resolvedBy: actor.id,
|
|
@@ -461,10 +498,11 @@ var PostServiceImpl = class {
|
|
|
461
498
|
actorId: actor.id
|
|
462
499
|
});
|
|
463
500
|
}
|
|
464
|
-
async spaceFeed(
|
|
465
|
-
const
|
|
501
|
+
async spaceFeed(communityId, spaceId, query, actor) {
|
|
502
|
+
const orgId = actor.orgId;
|
|
503
|
+
const space = await this.requireOpen(actor, spaceId, communityId);
|
|
466
504
|
if (query.topicId && !space.topics.some((topic) => topic.id === query.topicId)) throw new NotFoundError("Topic", query.topicId);
|
|
467
|
-
const page = await this.feedPage(
|
|
505
|
+
const page = await this.feedPage(actor, [spaceId], query);
|
|
468
506
|
this.logger.debug("space feed read", {
|
|
469
507
|
orgId,
|
|
470
508
|
spaceId,
|
|
@@ -472,9 +510,10 @@ var PostServiceImpl = class {
|
|
|
472
510
|
});
|
|
473
511
|
return page;
|
|
474
512
|
}
|
|
475
|
-
async memberFeed(
|
|
476
|
-
const
|
|
477
|
-
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);
|
|
478
517
|
this.logger.debug("member feed read", {
|
|
479
518
|
orgId,
|
|
480
519
|
communityId,
|
|
@@ -483,12 +522,13 @@ var PostServiceImpl = class {
|
|
|
483
522
|
});
|
|
484
523
|
return page;
|
|
485
524
|
}
|
|
486
|
-
async openSpaceIdsIn(
|
|
487
|
-
if (!await this.access.isCommunityMember(
|
|
488
|
-
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);
|
|
489
528
|
}
|
|
490
|
-
async listOwn(
|
|
491
|
-
const
|
|
529
|
+
async listOwn(communityId, query, actor) {
|
|
530
|
+
const orgId = actor.orgId;
|
|
531
|
+
const spaceIds = await this.openSpaceIdsIn(actor, communityId);
|
|
492
532
|
const page = await this.repo.listByAuthor(orgId, actor.id, spaceIds, query);
|
|
493
533
|
this.logger.debug("own posts read", {
|
|
494
534
|
orgId,
|
|
@@ -497,14 +537,15 @@ var PostServiceImpl = class {
|
|
|
497
537
|
});
|
|
498
538
|
return {
|
|
499
539
|
...page,
|
|
500
|
-
rows: await this.viewsOf(
|
|
540
|
+
rows: await this.viewsOf(actor, page.rows)
|
|
501
541
|
};
|
|
502
542
|
}
|
|
503
|
-
async listBookmarked(
|
|
504
|
-
const
|
|
543
|
+
async listBookmarked(communityId, query, actor) {
|
|
544
|
+
const orgId = actor.orgId;
|
|
545
|
+
const spaceIds = await this.openSpaceIdsIn(actor, communityId);
|
|
505
546
|
const page = await this.repo.listBookmarked(orgId, actor.id, spaceIds, {
|
|
506
547
|
...query,
|
|
507
|
-
visibleStatuses: this.visibleStatuses(actor)
|
|
548
|
+
visibleStatuses: this.visibleStatuses(actor, communityId)
|
|
508
549
|
});
|
|
509
550
|
this.logger.debug("bookmarked posts read", {
|
|
510
551
|
orgId,
|
|
@@ -513,27 +554,47 @@ var PostServiceImpl = class {
|
|
|
513
554
|
});
|
|
514
555
|
return {
|
|
515
556
|
...page,
|
|
516
|
-
rows: await this.viewsOf(
|
|
557
|
+
rows: await this.viewsOf(actor, page.rows)
|
|
517
558
|
};
|
|
518
559
|
}
|
|
519
|
-
async coverFile(
|
|
520
|
-
const post = await this.repo.find(orgId, postId);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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);
|
|
528
580
|
this.logger.debug("post read", {
|
|
529
|
-
orgId,
|
|
581
|
+
orgId: actor.orgId,
|
|
530
582
|
postId
|
|
531
583
|
});
|
|
532
|
-
return this.viewOf(
|
|
584
|
+
return this.viewOf(actor, post);
|
|
533
585
|
}
|
|
534
|
-
async moderationQueue(
|
|
535
|
-
|
|
536
|
-
|
|
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
|
+
}
|
|
537
598
|
const [pending, flags, openCommentFlags] = await Promise.all([
|
|
538
599
|
this.repo.listPending(orgId, spaceId, 100),
|
|
539
600
|
this.repo.listOpenFlags(orgId, spaceId, 100),
|
|
@@ -547,47 +608,67 @@ var PostServiceImpl = class {
|
|
|
547
608
|
commentFlags: openCommentFlags.length
|
|
548
609
|
});
|
|
549
610
|
return {
|
|
550
|
-
pending: await this.viewsOf(
|
|
611
|
+
pending: await this.viewsOf(actor, pending),
|
|
551
612
|
flags,
|
|
552
|
-
commentFlags: openCommentFlags
|
|
613
|
+
commentFlags: await this.flaggedCommentViews(actor, space.communityId, openCommentFlags)
|
|
553
614
|
};
|
|
554
615
|
}
|
|
555
|
-
|
|
556
|
-
|
|
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);
|
|
557
635
|
}
|
|
558
636
|
find(orgId, postId) {
|
|
559
637
|
return this.repo.find(orgId, postId);
|
|
560
638
|
}
|
|
561
|
-
async submittable(
|
|
562
|
-
if (
|
|
563
|
-
return this.access.canOpen(
|
|
564
|
-
id: post.orgUserId,
|
|
565
|
-
role
|
|
566
|
-
}, space);
|
|
639
|
+
async submittable(post, space, author) {
|
|
640
|
+
if (!this.topicFits(space, post.topicId)) return false;
|
|
641
|
+
return this.access.canOpen(space, author);
|
|
567
642
|
}
|
|
568
|
-
async feedPage(
|
|
569
|
-
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);
|
|
570
645
|
return {
|
|
571
646
|
...page,
|
|
572
|
-
rows: await this.viewsOf(
|
|
647
|
+
rows: await this.viewsOf(actor, page.rows)
|
|
573
648
|
};
|
|
574
649
|
}
|
|
575
|
-
async viewOf(
|
|
576
|
-
const [view] = await this.viewsOf(
|
|
650
|
+
async viewOf(actor, post) {
|
|
651
|
+
const [view] = await this.viewsOf(actor, [post]);
|
|
577
652
|
return view;
|
|
578
653
|
}
|
|
579
|
-
async viewsOf(
|
|
654
|
+
async viewsOf(actor, posts) {
|
|
580
655
|
if (posts.length === 0) return [];
|
|
656
|
+
const orgId = actor.orgId;
|
|
581
657
|
const postIds = posts.map((post) => post.id);
|
|
658
|
+
const authorIds = [...new Set(posts.map((post) => post.orgUserId))];
|
|
582
659
|
const now = /* @__PURE__ */ new Date();
|
|
583
|
-
const
|
|
584
|
-
|
|
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),
|
|
585
664
|
this.repo.reactionsOf(orgId, postIds, actor.id),
|
|
586
665
|
this.comments.publishedCommentCounts(orgId, postIds),
|
|
587
666
|
this.subscriptions.forViewer(orgId, actor.id, "post", postIds),
|
|
588
667
|
this.repo.bookmarkedAmong(orgId, postIds, actor.id),
|
|
589
|
-
mentionViews(this.mentions, this.profiles, orgId, "post", postIds)
|
|
668
|
+
mentionViews(this.mentions, this.profiles, orgId, "post", postIds),
|
|
669
|
+
this.images.memberUploads(orgId, [...new Set([...bodyAssets.values()].flat())])
|
|
590
670
|
]);
|
|
671
|
+
const uploadOf = new Map(uploads.map((upload) => [upload.id, upload]));
|
|
591
672
|
const subscriptionOf = new Map(subscribed.map((row) => [row.itemId, row]));
|
|
592
673
|
const saved = new Set(bookmarked);
|
|
593
674
|
return posts.map((post) => {
|
|
@@ -596,8 +677,17 @@ var PostServiceImpl = class {
|
|
|
596
677
|
const counts = reactions[post.id];
|
|
597
678
|
return {
|
|
598
679
|
...post,
|
|
599
|
-
coverUrl: post.coverAssetId ? this.fileUrls.
|
|
600
|
-
|
|
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)),
|
|
601
691
|
reactions: counts?.reactions ?? {},
|
|
602
692
|
viewerReaction: counts?.viewerReaction ?? null,
|
|
603
693
|
commentCount: commentCounts[post.id] ?? 0,
|
|
@@ -607,19 +697,21 @@ var PostServiceImpl = class {
|
|
|
607
697
|
};
|
|
608
698
|
});
|
|
609
699
|
}
|
|
610
|
-
async commitSubmission(orgId, post, space,
|
|
611
|
-
|
|
612
|
-
|
|
700
|
+
async commitSubmission(orgId, post, space, skips, actor, now) {
|
|
701
|
+
const mentioned = await this.mentionedOpeners(orgId, post);
|
|
702
|
+
return this.uow.run(async ({ posts, outbox }) => {
|
|
703
|
+
const next = await posts.updateIf(orgId, post.id, { status: post.status }, this.submissionOf(space.settings.requireReview, skips, now));
|
|
613
704
|
if (!next) throw new ConflictError("This post cannot be submitted");
|
|
614
|
-
await outbox.append([
|
|
705
|
+
await outbox.append([this.landed(orgId, next, this.meta(actor, post.status), mentioned)]);
|
|
615
706
|
return next;
|
|
616
707
|
});
|
|
617
708
|
}
|
|
618
|
-
async moderate(
|
|
619
|
-
const
|
|
620
|
-
|
|
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);
|
|
621
713
|
if (post.status === "removed") throw new ConflictError("This post was removed");
|
|
622
|
-
if (unchanged(post)) return this.viewOf(
|
|
714
|
+
if (unchanged(post)) return this.viewOf(actor, post);
|
|
623
715
|
const updated = await this.uow.run(async ({ posts, outbox }) => {
|
|
624
716
|
const next = await this.mustUpdate(posts, orgId, postId, patch);
|
|
625
717
|
await outbox.append([this.updated(orgId, next, this.meta(actor, next.status === "pending" ? post.status : void 0))]);
|
|
@@ -630,7 +722,7 @@ var PostServiceImpl = class {
|
|
|
630
722
|
postId,
|
|
631
723
|
actorId: actor.id
|
|
632
724
|
});
|
|
633
|
-
return this.viewOf(
|
|
725
|
+
return this.viewOf(actor, updated);
|
|
634
726
|
}
|
|
635
727
|
async mustUpdate(posts, orgId, postId, patch) {
|
|
636
728
|
const next = await posts.update(orgId, postId, patch);
|
|
@@ -654,10 +746,10 @@ var PostServiceImpl = class {
|
|
|
654
746
|
removedBy: null
|
|
655
747
|
};
|
|
656
748
|
}
|
|
657
|
-
return this.submissionOf(space.settings.requireReview, actor.
|
|
749
|
+
return this.submissionOf(space.settings.requireReview, skipsReview(actor.permissions, { communityId: space.communityId }), now);
|
|
658
750
|
}
|
|
659
|
-
submissionOf(requireReview,
|
|
660
|
-
if (requireReview && !
|
|
751
|
+
submissionOf(requireReview, skips, now) {
|
|
752
|
+
if (requireReview && !skips) return {
|
|
661
753
|
status: "pending",
|
|
662
754
|
scheduledAt: null,
|
|
663
755
|
publishedAt: null,
|
|
@@ -681,20 +773,31 @@ var PostServiceImpl = class {
|
|
|
681
773
|
throw new NotFoundError("Asset", assetId);
|
|
682
774
|
}
|
|
683
775
|
}
|
|
684
|
-
async
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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;
|
|
688
791
|
}
|
|
689
|
-
async requireOpen(
|
|
690
|
-
const space = await this.openSpace(
|
|
792
|
+
async requireOpen(actor, spaceId, communityId) {
|
|
793
|
+
const space = await this.openSpace(actor, spaceId, communityId);
|
|
691
794
|
if (!space) throw new NotFoundError("Space", spaceId);
|
|
692
795
|
return space;
|
|
693
796
|
}
|
|
694
|
-
async loadVisible(
|
|
695
|
-
const post = await this.repo.find(orgId, postId);
|
|
696
|
-
if (!post || communityId
|
|
697
|
-
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);
|
|
698
801
|
if (!space || !this.canSee(actor, post)) throw new NotFoundError("Post", postId);
|
|
699
802
|
return {
|
|
700
803
|
post,
|
|
@@ -702,25 +805,34 @@ var PostServiceImpl = class {
|
|
|
702
805
|
};
|
|
703
806
|
}
|
|
704
807
|
canSee(actor, post) {
|
|
705
|
-
return post.orgUserId === actor.id || this.visibleStatuses(actor).includes(post.status);
|
|
808
|
+
return post.orgUserId === actor.id || this.visibleStatuses(actor, post.communityId).includes(post.status);
|
|
706
809
|
}
|
|
707
|
-
visibleStatuses(actor) {
|
|
708
|
-
return this.
|
|
810
|
+
visibleStatuses(actor, communityId) {
|
|
811
|
+
return this.moderates(actor.permissions, communityId) ? MODERATOR_VISIBLE : PUBLISHED_ONLY;
|
|
709
812
|
}
|
|
710
|
-
|
|
711
|
-
return
|
|
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
|
+
}
|
|
712
827
|
}
|
|
713
828
|
assertAuthor(actor, post) {
|
|
714
829
|
if (post.orgUserId !== actor.id) throw new ForbiddenError("You can change only your own post");
|
|
715
830
|
}
|
|
716
831
|
topicFits(space, topicId) {
|
|
717
|
-
|
|
718
|
-
return space.topics.some((topic) => topic.id === topicId);
|
|
832
|
+
return topicId === null || space.topics.some((topic) => topic.id === topicId);
|
|
719
833
|
}
|
|
720
834
|
assertTopic(space, topicId) {
|
|
721
|
-
if (this.topicFits(space, topicId))
|
|
722
|
-
if (topicId === null) throw new InvalidInputError("This space requires a topic");
|
|
723
|
-
throw new NotFoundError("Topic", topicId);
|
|
835
|
+
if (topicId !== null && !this.topicFits(space, topicId)) throw new NotFoundError("Topic", topicId);
|
|
724
836
|
}
|
|
725
837
|
patchChanges(post, patch) {
|
|
726
838
|
return Object.keys(patch).some((key) => {
|
|
@@ -731,15 +843,17 @@ var PostServiceImpl = class {
|
|
|
731
843
|
});
|
|
732
844
|
}
|
|
733
845
|
async mentionable(orgId, spaceId, title, body) {
|
|
734
|
-
return this.openersOf(orgId, spaceId, mentionTokens(title, body));
|
|
846
|
+
return this.openersOf(orgId, spaceId, [.../* @__PURE__ */ new Set([...mentionTokens(title), ...body])]);
|
|
735
847
|
}
|
|
736
848
|
async openersOf(orgId, spaceId, orgUserIds) {
|
|
737
849
|
if (orgUserIds.length === 0) return [];
|
|
738
850
|
return keepAllowed(orgUserIds, await this.access.spaceOpenersAmong(orgId, spaceId, orgUserIds));
|
|
739
851
|
}
|
|
740
|
-
async
|
|
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) {
|
|
741
856
|
if (post.status !== "published") return this.updated(orgId, post, metadata);
|
|
742
|
-
const mentioned = await this.openersOf(orgId, post.spaceId, await mentions.ofItem(orgId, "post", post.id));
|
|
743
857
|
return this.published(orgId, post, metadata, mentioned);
|
|
744
858
|
}
|
|
745
859
|
published(orgId, post, metadata, mentionedOrgUserIds) {
|
|
@@ -764,7 +878,7 @@ var PostServiceImpl = class {
|
|
|
764
878
|
}
|
|
765
879
|
meta(actor, previousStatus) {
|
|
766
880
|
return {
|
|
767
|
-
...actor ? {
|
|
881
|
+
...actor ? { actorId: actor.id } : {},
|
|
768
882
|
...previousStatus !== void 0 ? { previousStatus } : {}
|
|
769
883
|
};
|
|
770
884
|
}
|