@headless-lms/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/events.d.ts +27 -21
- 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 +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 +230 -56
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.js +69 -4
- 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 +3 -3
- 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 +13 -13
- 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 +91 -62
- 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 +413 -72
- 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 +99 -67
- package/dist/content/service.js +680 -139
- 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 +393 -161
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/events.d.ts +77 -39
- 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 +268 -154
- 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 +252 -188
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +8 -8
- package/dist/discussion/subscriptions.js +29 -13
- 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 +20 -20
- 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 +370 -163
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +3 -3
- package/dist/identity/devices.js +7 -5
- package/dist/identity/devices.js.map +1 -1
- package/dist/identity/events.d.ts +2 -2
- 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 +3 -2
- 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 +17 -13
- 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 +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 +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/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 +184 -65
- package/dist/organizations/service.d.ts +102 -35
- package/dist/organizations/service.js +1245 -328
- 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 +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 +26 -5
- package/dist/reporting/member/ports.d.ts +48 -29
- package/dist/reporting/member/service.d.ts +56 -29
- package/dist/reporting/member/service.js +328 -113
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +22 -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 +537 -13
- package/dist/types/schemas/content.js +201 -7
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +282 -59
- package/dist/types/schemas/discussion.js +77 -3
- 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/index.d.ts +5 -5
- package/dist/types/schemas/index.js +5 -5
- package/dist/types/schemas/notifications.d.ts +13 -12
- package/dist/types/schemas/notifications.js +3 -2
- package/dist/types/schemas/notifications.js.map +1 -1
- 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 +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/content/service.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { spaceSettingsDefaults } from "../types/schemas/content.js";
|
|
2
|
-
import { ConflictError, ForbiddenError, InvalidInputError, NotFoundError } from "../shared/errors.js";
|
|
3
2
|
import { noopLogger } from "../shared/logger.js";
|
|
3
|
+
import { ConflictError, ForbiddenError, InvalidInputError, NotFoundError } from "../shared/errors.js";
|
|
4
4
|
import { contentEvents } from "./events.js";
|
|
5
|
+
import { EVENT_LEADS, EVENT_STARTING_SOON_JOB } from "./jobs.js";
|
|
6
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
7
|
//#region src/content/service.ts
|
|
6
8
|
/** URL-safe slug derived from a title. Domain rule owned by the service. */
|
|
7
9
|
function slugify(title) {
|
|
@@ -11,10 +13,28 @@ function isPublishedActivity(settings) {
|
|
|
11
13
|
return settings?.published !== false;
|
|
12
14
|
}
|
|
13
15
|
function actorMetadata(actor) {
|
|
14
|
-
return {
|
|
16
|
+
return { actorId: actor.id };
|
|
17
|
+
}
|
|
18
|
+
const COMMUNITY_READERS = ["communities:read", "communities:moderate"];
|
|
19
|
+
function eventChange(actor, previous, next) {
|
|
20
|
+
return {
|
|
21
|
+
actorId: actor.id,
|
|
22
|
+
previousStatus: previous.status,
|
|
23
|
+
previousType: previous.settings.type,
|
|
24
|
+
previousStartsAt: previous.startsAt.toISOString(),
|
|
25
|
+
previousEndsAt: previous.endsAt.toISOString(),
|
|
26
|
+
locationChanged: !isDeepStrictEqual(previous.location, next.location)
|
|
27
|
+
};
|
|
15
28
|
}
|
|
16
|
-
function
|
|
17
|
-
if (
|
|
29
|
+
function assertEventTimes(startsAt, endsAt) {
|
|
30
|
+
if (endsAt.getTime() <= startsAt.getTime()) throw new InvalidInputError("An event must end after it starts");
|
|
31
|
+
}
|
|
32
|
+
function startingSoonKey(eventId, lead) {
|
|
33
|
+
return `${eventId}:${lead}`;
|
|
34
|
+
}
|
|
35
|
+
const EVENT_LEAD_KEYS = Object.keys(EVENT_LEADS);
|
|
36
|
+
function shownPublicly(item) {
|
|
37
|
+
return item.status === "published" || item.type === "event" && item.status === "canceled";
|
|
18
38
|
}
|
|
19
39
|
function sameIdSet(current, requested) {
|
|
20
40
|
if (current.length !== requested.length) return false;
|
|
@@ -27,6 +47,7 @@ var ContentService = class {
|
|
|
27
47
|
libraryAssets;
|
|
28
48
|
files;
|
|
29
49
|
fileUrls;
|
|
50
|
+
scheduler;
|
|
30
51
|
logger;
|
|
31
52
|
constructor(params) {
|
|
32
53
|
this.repo = params.repo;
|
|
@@ -34,14 +55,49 @@ var ContentService = class {
|
|
|
34
55
|
this.libraryAssets = params.libraryAssets;
|
|
35
56
|
this.files = params.files;
|
|
36
57
|
this.fileUrls = params.fileUrls;
|
|
58
|
+
this.scheduler = params.scheduler;
|
|
37
59
|
this.logger = params.logger ?? noopLogger;
|
|
38
60
|
}
|
|
61
|
+
require(actor, permission, on) {
|
|
62
|
+
this.requireAny(actor, [permission], on);
|
|
63
|
+
}
|
|
64
|
+
requireAny(actor, permissions, on) {
|
|
65
|
+
if (!permissions.some((permission) => actor.permissions.has(permission, on))) {
|
|
66
|
+
this.logger.warn("content action refused", {
|
|
67
|
+
orgId: actor.orgId,
|
|
68
|
+
actorId: actor.id,
|
|
69
|
+
permission: permissions.join(" or "),
|
|
70
|
+
contentId: on
|
|
71
|
+
});
|
|
72
|
+
throw new ForbiddenError(`You need ${permissions.join(" or ")}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
visibleIds(actor, permissions, what) {
|
|
76
|
+
const wheres = permissions.map((permission) => actor.permissions.where(permission));
|
|
77
|
+
if (wheres.some((where) => where.org)) return;
|
|
78
|
+
const ids = [...new Set(wheres.flatMap((where) => where.content))];
|
|
79
|
+
if (ids.length === 0) {
|
|
80
|
+
this.logger.warn("content list refused", {
|
|
81
|
+
orgId: actor.orgId,
|
|
82
|
+
actorId: actor.id,
|
|
83
|
+
permission: permissions.join(" or ")
|
|
84
|
+
});
|
|
85
|
+
throw new ForbiddenError(`You cannot see ${what}`);
|
|
86
|
+
}
|
|
87
|
+
return ids;
|
|
88
|
+
}
|
|
39
89
|
withThumbnail(item) {
|
|
40
90
|
return {
|
|
41
91
|
...item,
|
|
42
|
-
thumbnailUrl: item.thumbnailAssetId ? this.fileUrls.
|
|
92
|
+
thumbnailUrl: !item.thumbnailAssetId ? null : shownPublicly(item) ? this.fileUrls.publicImage(item.orgId, item.id, item.thumbnailAssetId) : this.fileUrls.thumbnail(item.type, item.id)
|
|
43
93
|
};
|
|
44
94
|
}
|
|
95
|
+
syncThumbnail(item) {
|
|
96
|
+
return this.files.syncPublicImage(item.orgId, item.id, shownPublicly(item) ? item.thumbnailAssetId : null);
|
|
97
|
+
}
|
|
98
|
+
withdrawThumbnail(orgId, id) {
|
|
99
|
+
return this.files.syncPublicImage(orgId, id, null);
|
|
100
|
+
}
|
|
45
101
|
pageWithThumbnails(page) {
|
|
46
102
|
return {
|
|
47
103
|
...page,
|
|
@@ -59,14 +115,76 @@ var ContentService = class {
|
|
|
59
115
|
}] : [];
|
|
60
116
|
});
|
|
61
117
|
}
|
|
62
|
-
async
|
|
63
|
-
|
|
118
|
+
async eventFiles(orgId, links) {
|
|
119
|
+
const ready = await this.files.readyFiles(orgId, links.map((link) => link.assetId));
|
|
120
|
+
return links.flatMap((link) => {
|
|
121
|
+
const file = ready[link.assetId];
|
|
122
|
+
return file ? [{
|
|
123
|
+
...link,
|
|
124
|
+
...file,
|
|
125
|
+
url: this.fileUrls.eventFile(link.eventId, link.assetId)
|
|
126
|
+
}] : [];
|
|
127
|
+
});
|
|
64
128
|
}
|
|
65
|
-
async
|
|
66
|
-
const
|
|
129
|
+
async assertAttachable(actor, itemId, requested, attached, user) {
|
|
130
|
+
const ids = [...new Set(requested.filter((id) => typeof id === "string"))];
|
|
131
|
+
if (ids.length === 0) return;
|
|
132
|
+
const held = new Set(await attached());
|
|
133
|
+
const added = ids.filter((id) => !held.has(id));
|
|
134
|
+
if (added.length === 0) return;
|
|
135
|
+
const orgId = actor.orgId;
|
|
136
|
+
const found = new Map((await this.libraryAssets.libraryAssetsFor(orgId, added)).map((asset) => [asset.id, asset]));
|
|
137
|
+
const missing = added.filter((id) => !found.has(id));
|
|
138
|
+
if (missing.length > 0) {
|
|
139
|
+
this.logger.warn("library assets refused", {
|
|
140
|
+
orgId,
|
|
141
|
+
contentId: itemId,
|
|
142
|
+
assetIds: missing
|
|
143
|
+
});
|
|
144
|
+
throw new ConflictError(`${user} can only use ready files from the media library. Not available: ${missing.join(", ")}`);
|
|
145
|
+
}
|
|
146
|
+
if (actor.permissions.has("media:read")) return;
|
|
147
|
+
const foreign = added.filter((id) => found.get(id)?.contentId !== itemId);
|
|
148
|
+
if (foreign.length > 0) {
|
|
149
|
+
this.logger.warn("library assets refused — not uploaded for this item", {
|
|
150
|
+
orgId,
|
|
151
|
+
actorId: actor.id,
|
|
152
|
+
contentId: itemId,
|
|
153
|
+
assetIds: foreign
|
|
154
|
+
});
|
|
155
|
+
throw new ForbiddenError(`You need media:read to use files that were not uploaded for this item: ${foreign.join(", ")}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async courseAssetIds(orgId, courseId) {
|
|
159
|
+
const [course, links] = await Promise.all([this.repo.findCourseById(orgId, courseId), this.repo.listActivityAssetsForCourse(orgId, courseId)]);
|
|
160
|
+
return [course?.thumbnailAssetId ?? null, ...links.map((link) => link.assetId)];
|
|
161
|
+
}
|
|
162
|
+
async downloadAssetIds(orgId, downloadId) {
|
|
163
|
+
const [download, links] = await Promise.all([this.repo.getDownload(orgId, downloadId), this.repo.listDownloadAssets(orgId, downloadId)]);
|
|
164
|
+
return [download?.thumbnailAssetId ?? null, ...links.map((link) => link.assetId)];
|
|
165
|
+
}
|
|
166
|
+
async eventAssetIds(orgId, eventId) {
|
|
167
|
+
const [event, links] = await Promise.all([this.repo.findEventById(orgId, eventId), this.repo.listEventAssets(orgId, eventId)]);
|
|
168
|
+
if (!event) throw new NotFoundError("Event", eventId);
|
|
169
|
+
return [event.thumbnailAssetId, ...links.map((link) => link.assetId)];
|
|
170
|
+
}
|
|
171
|
+
async listCourses(query, actor) {
|
|
172
|
+
const ids = this.visibleIds(actor, ["courses:read"], "courses");
|
|
173
|
+
return this.pageWithThumbnails(await this.repo.listCourses(actor.orgId, query, ids));
|
|
174
|
+
}
|
|
175
|
+
async getCourse(id, actor) {
|
|
176
|
+
const course = await this.findCourse(id, actor);
|
|
177
|
+
if (!course) throw new NotFoundError("Course", id);
|
|
178
|
+
return course;
|
|
179
|
+
}
|
|
180
|
+
async findCourse(id, actor) {
|
|
181
|
+
this.require(actor, "courses:read", id);
|
|
182
|
+
const course = await this.repo.findCourseById(actor.orgId, id);
|
|
67
183
|
return course ? this.withThumbnail(course) : null;
|
|
68
184
|
}
|
|
69
|
-
async createCourse(
|
|
185
|
+
async createCourse(input, actor) {
|
|
186
|
+
this.require(actor, "courses:write");
|
|
187
|
+
const orgId = actor.orgId;
|
|
70
188
|
const course = await this.uow.run(async ({ content, outbox }) => {
|
|
71
189
|
const created = await content.createCourse(orgId, input, slugify(input.title));
|
|
72
190
|
await outbox.append([contentEvents.courseCreated.make({
|
|
@@ -78,12 +196,15 @@ var ContentService = class {
|
|
|
78
196
|
this.logger.info("course created", {
|
|
79
197
|
orgId,
|
|
80
198
|
courseId: course.id,
|
|
81
|
-
title: course.title
|
|
199
|
+
title: course.title,
|
|
200
|
+
actorId: actor.id
|
|
82
201
|
});
|
|
83
202
|
return this.withThumbnail(course);
|
|
84
203
|
}
|
|
85
|
-
async updateCourse(
|
|
86
|
-
|
|
204
|
+
async updateCourse(id, patch, actor) {
|
|
205
|
+
this.require(actor, "courses:write", id);
|
|
206
|
+
const orgId = actor.orgId;
|
|
207
|
+
await this.assertAttachable(actor, id, [patch.thumbnailAssetId], () => this.courseAssetIds(orgId, id), "A course");
|
|
87
208
|
const course = await this.uow.run(async ({ content, outbox }) => {
|
|
88
209
|
const updated = await content.updateCourse(orgId, id, patch);
|
|
89
210
|
if (!updated) {
|
|
@@ -102,11 +223,15 @@ var ContentService = class {
|
|
|
102
223
|
this.logger.info("course updated", {
|
|
103
224
|
orgId,
|
|
104
225
|
courseId: id,
|
|
105
|
-
fields: Object.keys(patch)
|
|
226
|
+
fields: Object.keys(patch),
|
|
227
|
+
actorId: actor.id
|
|
106
228
|
});
|
|
229
|
+
await this.syncThumbnail(course);
|
|
107
230
|
return this.withThumbnail(course);
|
|
108
231
|
}
|
|
109
|
-
async patchCourseSettings(
|
|
232
|
+
async patchCourseSettings(id, value, actor) {
|
|
233
|
+
this.require(actor, "courses:write", id);
|
|
234
|
+
const orgId = actor.orgId;
|
|
110
235
|
const merged = await this.uow.run(async ({ content, outbox }) => {
|
|
111
236
|
const course = await content.findCourseById(orgId, id);
|
|
112
237
|
if (!course) {
|
|
@@ -129,11 +254,14 @@ var ContentService = class {
|
|
|
129
254
|
this.logger.info("course settings patched", {
|
|
130
255
|
orgId,
|
|
131
256
|
courseId: id,
|
|
132
|
-
value
|
|
257
|
+
value,
|
|
258
|
+
actorId: actor.id
|
|
133
259
|
});
|
|
134
260
|
return merged;
|
|
135
261
|
}
|
|
136
|
-
async deleteCourse(
|
|
262
|
+
async deleteCourse(id, actor) {
|
|
263
|
+
this.require(actor, "courses:delete", id);
|
|
264
|
+
const orgId = actor.orgId;
|
|
137
265
|
await this.uow.run(async ({ content, outbox }) => {
|
|
138
266
|
const course = await content.findCourseById(orgId, id);
|
|
139
267
|
if (!course) {
|
|
@@ -152,32 +280,44 @@ var ContentService = class {
|
|
|
152
280
|
});
|
|
153
281
|
this.logger.info("course deleted", {
|
|
154
282
|
orgId,
|
|
155
|
-
courseId: id
|
|
283
|
+
courseId: id,
|
|
284
|
+
actorId: actor.id
|
|
156
285
|
});
|
|
286
|
+
await this.withdrawThumbnail(orgId, id);
|
|
157
287
|
}
|
|
158
|
-
listCourseModules(
|
|
159
|
-
|
|
288
|
+
async listCourseModules(courseId, actor) {
|
|
289
|
+
this.require(actor, "courses:read", courseId);
|
|
290
|
+
return this.repo.listCourseModules(actor.orgId, courseId);
|
|
160
291
|
}
|
|
161
|
-
listCourseActivities(
|
|
162
|
-
|
|
292
|
+
async listCourseActivities(courseId, actor) {
|
|
293
|
+
this.require(actor, "courses:read", courseId);
|
|
294
|
+
return this.repo.listActivitiesForCourse(actor.orgId, courseId);
|
|
163
295
|
}
|
|
164
|
-
async listCourseActivityViews(
|
|
165
|
-
|
|
296
|
+
async listCourseActivityViews(courseId, actor) {
|
|
297
|
+
this.require(actor, "courses:read", courseId);
|
|
298
|
+
const [activities, links] = await Promise.all([this.repo.listActivitiesForCourse(actor.orgId, courseId), this.repo.listActivityAssetsForCourse(actor.orgId, courseId)]);
|
|
166
299
|
return activities.map((activity) => ({
|
|
167
300
|
...activity,
|
|
168
301
|
assetUrls: Object.fromEntries(links.filter((link) => link.activityId === activity.id).map((link) => [link.assetId, this.fileUrls.activityAsset(activity.id, link.assetId)]))
|
|
169
302
|
}));
|
|
170
303
|
}
|
|
171
|
-
listCourseActivityAssets(
|
|
172
|
-
|
|
304
|
+
async listCourseActivityAssets(courseId, actor) {
|
|
305
|
+
this.require(actor, "courses:read", courseId);
|
|
306
|
+
return this.repo.listActivityAssetsForCourse(actor.orgId, courseId);
|
|
173
307
|
}
|
|
174
|
-
getActivity(
|
|
175
|
-
|
|
308
|
+
async getActivity(activityId, actor) {
|
|
309
|
+
const activity = await this.repo.findActivity(actor.orgId, activityId);
|
|
310
|
+
if (activity) this.require(actor, "courses:read", activity.courseId);
|
|
311
|
+
return activity;
|
|
176
312
|
}
|
|
177
|
-
getModule(
|
|
178
|
-
|
|
313
|
+
async getModule(moduleId, actor) {
|
|
314
|
+
const module = await this.repo.findModule(actor.orgId, moduleId);
|
|
315
|
+
if (module) this.require(actor, "courses:read", module.courseId);
|
|
316
|
+
return module;
|
|
179
317
|
}
|
|
180
|
-
async reorderModules(
|
|
318
|
+
async reorderModules(courseId, orderedIds, actor) {
|
|
319
|
+
this.require(actor, "courses:write", courseId);
|
|
320
|
+
const orgId = actor.orgId;
|
|
181
321
|
const modules = await this.uow.run(async ({ content, outbox }) => {
|
|
182
322
|
const reordered = await content.reorderModules(orgId, courseId, orderedIds);
|
|
183
323
|
await outbox.append([contentEvents.modulesReordered.make({
|
|
@@ -193,7 +333,9 @@ var ContentService = class {
|
|
|
193
333
|
});
|
|
194
334
|
return modules;
|
|
195
335
|
}
|
|
196
|
-
async createModule(
|
|
336
|
+
async createModule(courseId, title, actor) {
|
|
337
|
+
this.require(actor, "courses:write", courseId);
|
|
338
|
+
const orgId = actor.orgId;
|
|
197
339
|
const { modules, created } = await this.uow.run(async ({ content, outbox }) => {
|
|
198
340
|
const all = await content.createModule(orgId, courseId, title);
|
|
199
341
|
const module = all.at(-1);
|
|
@@ -211,11 +353,14 @@ var ContentService = class {
|
|
|
211
353
|
orgId,
|
|
212
354
|
courseId,
|
|
213
355
|
moduleId: created.id,
|
|
214
|
-
title
|
|
356
|
+
title,
|
|
357
|
+
actorId: actor.id
|
|
215
358
|
});
|
|
216
359
|
return modules;
|
|
217
360
|
}
|
|
218
|
-
async updateModule(
|
|
361
|
+
async updateModule(courseId, moduleId, title, actor) {
|
|
362
|
+
this.require(actor, "courses:write", courseId);
|
|
363
|
+
const orgId = actor.orgId;
|
|
219
364
|
const modules = await this.uow.run(async ({ content, outbox }) => {
|
|
220
365
|
const existing = await content.findModule(orgId, moduleId);
|
|
221
366
|
if (!existing || existing.courseId !== courseId) {
|
|
@@ -239,11 +384,14 @@ var ContentService = class {
|
|
|
239
384
|
orgId,
|
|
240
385
|
courseId,
|
|
241
386
|
moduleId,
|
|
242
|
-
title
|
|
387
|
+
title,
|
|
388
|
+
actorId: actor.id
|
|
243
389
|
});
|
|
244
390
|
return modules;
|
|
245
391
|
}
|
|
246
|
-
async deleteModule(
|
|
392
|
+
async deleteModule(courseId, moduleId, actor) {
|
|
393
|
+
this.require(actor, "courses:write", courseId);
|
|
394
|
+
const orgId = actor.orgId;
|
|
247
395
|
const modules = await this.uow.run(async ({ content, outbox }) => {
|
|
248
396
|
const module = await content.findModule(orgId, moduleId);
|
|
249
397
|
if (!module || module.courseId !== courseId) {
|
|
@@ -264,11 +412,14 @@ var ContentService = class {
|
|
|
264
412
|
this.logger.info("module deleted", {
|
|
265
413
|
orgId,
|
|
266
414
|
courseId,
|
|
267
|
-
moduleId
|
|
415
|
+
moduleId,
|
|
416
|
+
actorId: actor.id
|
|
268
417
|
});
|
|
269
418
|
return modules;
|
|
270
419
|
}
|
|
271
|
-
async reorderActivities(
|
|
420
|
+
async reorderActivities(courseId, moduleId, orderedIds, actor) {
|
|
421
|
+
this.require(actor, "courses:write", courseId);
|
|
422
|
+
const orgId = actor.orgId;
|
|
272
423
|
const modules = await this.uow.run(async ({ content, outbox }) => {
|
|
273
424
|
const reordered = await content.reorderActivities(orgId, courseId, moduleId, orderedIds);
|
|
274
425
|
const module = reordered.find((m) => m.id === moduleId);
|
|
@@ -286,21 +437,10 @@ var ContentService = class {
|
|
|
286
437
|
});
|
|
287
438
|
return modules;
|
|
288
439
|
}
|
|
289
|
-
async
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const refused = ids.filter((id) => !library.has(id));
|
|
294
|
-
if (refused.length > 0) {
|
|
295
|
-
this.logger.warn("library assets refused", {
|
|
296
|
-
orgId,
|
|
297
|
-
assetIds: refused
|
|
298
|
-
});
|
|
299
|
-
throw new ConflictError(`${user} can only use ready files from the media library. Not available: ${refused.join(", ")}`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
async saveActivity(orgId, courseId, moduleId, input, activityId) {
|
|
303
|
-
await this.assertLibraryAssets(orgId, input.assetIds ?? []);
|
|
440
|
+
async saveActivity(courseId, moduleId, input, activityId, actor) {
|
|
441
|
+
this.require(actor, "courses:write", courseId);
|
|
442
|
+
const orgId = actor.orgId;
|
|
443
|
+
await this.assertAttachable(actor, courseId, input.assetIds ?? [], () => this.courseAssetIds(orgId, courseId), "An activity");
|
|
304
444
|
const { modules, activity } = await this.uow.run(async ({ content, outbox }) => {
|
|
305
445
|
let wasPublished = false;
|
|
306
446
|
if (activityId) {
|
|
@@ -326,7 +466,7 @@ var ContentService = class {
|
|
|
326
466
|
throw new NotFoundError("Module", moduleId);
|
|
327
467
|
}
|
|
328
468
|
}
|
|
329
|
-
const { modules: all, activity: saved } = await content.saveActivity(orgId, courseId, moduleId, input, activityId);
|
|
469
|
+
const { modules: all, activity: saved } = await content.saveActivity(orgId, courseId, moduleId, input, activityId ?? void 0);
|
|
330
470
|
const events = [activityId ? contentEvents.activityUpdated.make({
|
|
331
471
|
orgId,
|
|
332
472
|
data: saved
|
|
@@ -354,11 +494,14 @@ var ContentService = class {
|
|
|
354
494
|
orgId,
|
|
355
495
|
courseId,
|
|
356
496
|
moduleId,
|
|
357
|
-
activityId: activity.id
|
|
497
|
+
activityId: activity.id,
|
|
498
|
+
actorId: actor.id
|
|
358
499
|
});
|
|
359
500
|
return modules;
|
|
360
501
|
}
|
|
361
|
-
async deleteActivity(
|
|
502
|
+
async deleteActivity(courseId, moduleId, activityId, actor) {
|
|
503
|
+
this.require(actor, "courses:write", courseId);
|
|
504
|
+
const orgId = actor.orgId;
|
|
362
505
|
const modules = await this.uow.run(async ({ content, outbox }) => {
|
|
363
506
|
const activity = await content.findActivity(orgId, activityId);
|
|
364
507
|
if (!activity || activity.moduleId !== moduleId || activity.courseId !== courseId) {
|
|
@@ -381,18 +524,28 @@ var ContentService = class {
|
|
|
381
524
|
orgId,
|
|
382
525
|
courseId,
|
|
383
526
|
moduleId,
|
|
384
|
-
activityId
|
|
527
|
+
activityId,
|
|
528
|
+
actorId: actor.id
|
|
385
529
|
});
|
|
386
530
|
return modules;
|
|
387
531
|
}
|
|
388
|
-
async listDownloads(
|
|
389
|
-
|
|
532
|
+
async listDownloads(query, actor) {
|
|
533
|
+
const ids = this.visibleIds(actor, ["downloads:read"], "downloads");
|
|
534
|
+
return this.pageWithThumbnails(await this.repo.listDownloads(actor.orgId, query, ids));
|
|
390
535
|
}
|
|
391
|
-
async getDownload(
|
|
392
|
-
const download = await this.
|
|
536
|
+
async getDownload(id, actor) {
|
|
537
|
+
const download = await this.findDownload(id, actor);
|
|
538
|
+
if (!download) throw new NotFoundError("Download", id);
|
|
539
|
+
return download;
|
|
540
|
+
}
|
|
541
|
+
async findDownload(id, actor) {
|
|
542
|
+
this.require(actor, "downloads:read", id);
|
|
543
|
+
const download = await this.repo.getDownload(actor.orgId, id);
|
|
393
544
|
return download ? this.withThumbnail(download) : null;
|
|
394
545
|
}
|
|
395
|
-
async createDownload(
|
|
546
|
+
async createDownload(input, actor) {
|
|
547
|
+
this.require(actor, "downloads:write");
|
|
548
|
+
const orgId = actor.orgId;
|
|
396
549
|
const download = await this.uow.run(async ({ content, outbox }) => {
|
|
397
550
|
const created = await content.createDownload(orgId, input, slugify(input.title));
|
|
398
551
|
await outbox.append([contentEvents.downloadCreated.make({
|
|
@@ -404,12 +557,15 @@ var ContentService = class {
|
|
|
404
557
|
this.logger.info("download created", {
|
|
405
558
|
orgId,
|
|
406
559
|
downloadId: download.id,
|
|
407
|
-
title: download.title
|
|
560
|
+
title: download.title,
|
|
561
|
+
actorId: actor.id
|
|
408
562
|
});
|
|
409
563
|
return this.withThumbnail(download);
|
|
410
564
|
}
|
|
411
|
-
async updateDownload(
|
|
412
|
-
|
|
565
|
+
async updateDownload(id, patch, actor) {
|
|
566
|
+
this.require(actor, "downloads:write", id);
|
|
567
|
+
const orgId = actor.orgId;
|
|
568
|
+
await this.assertAttachable(actor, id, [patch.thumbnailAssetId], () => this.downloadAssetIds(orgId, id), "A download");
|
|
413
569
|
const download = await this.uow.run(async ({ content, outbox }) => {
|
|
414
570
|
const updated = await content.updateDownload(orgId, id, patch);
|
|
415
571
|
if (!updated) {
|
|
@@ -428,11 +584,15 @@ var ContentService = class {
|
|
|
428
584
|
this.logger.info("download updated", {
|
|
429
585
|
orgId,
|
|
430
586
|
downloadId: id,
|
|
431
|
-
fields: Object.keys(patch)
|
|
587
|
+
fields: Object.keys(patch),
|
|
588
|
+
actorId: actor.id
|
|
432
589
|
});
|
|
590
|
+
await this.syncThumbnail(download);
|
|
433
591
|
return this.withThumbnail(download);
|
|
434
592
|
}
|
|
435
|
-
async removeDownload(
|
|
593
|
+
async removeDownload(id, actor) {
|
|
594
|
+
this.require(actor, "downloads:delete", id);
|
|
595
|
+
const orgId = actor.orgId;
|
|
436
596
|
await this.uow.run(async ({ content, outbox }) => {
|
|
437
597
|
const download = await content.getDownload(orgId, id);
|
|
438
598
|
if (!download) {
|
|
@@ -451,14 +611,19 @@ var ContentService = class {
|
|
|
451
611
|
});
|
|
452
612
|
this.logger.info("download deleted", {
|
|
453
613
|
orgId,
|
|
454
|
-
downloadId: id
|
|
614
|
+
downloadId: id,
|
|
615
|
+
actorId: actor.id
|
|
455
616
|
});
|
|
617
|
+
await this.withdrawThumbnail(orgId, id);
|
|
456
618
|
}
|
|
457
|
-
async listDownloadAssets(
|
|
458
|
-
|
|
619
|
+
async listDownloadAssets(downloadId, actor) {
|
|
620
|
+
this.require(actor, "downloads:read", downloadId);
|
|
621
|
+
return this.downloadFiles(actor.orgId, await this.repo.listDownloadAssets(actor.orgId, downloadId));
|
|
459
622
|
}
|
|
460
|
-
async addDownloadAsset(
|
|
461
|
-
|
|
623
|
+
async addDownloadAsset(downloadId, input, actor) {
|
|
624
|
+
this.require(actor, "downloads:write", downloadId);
|
|
625
|
+
const orgId = actor.orgId;
|
|
626
|
+
await this.assertAttachable(actor, downloadId, [input.assetId], () => this.downloadAssetIds(orgId, downloadId), "A download");
|
|
462
627
|
const assets = await this.uow.run(async ({ content, outbox }) => {
|
|
463
628
|
const assets = await content.addDownloadAsset(orgId, downloadId, input);
|
|
464
629
|
await outbox.append([contentEvents.downloadAssetAdded.make({
|
|
@@ -473,11 +638,14 @@ var ContentService = class {
|
|
|
473
638
|
this.logger.info("download asset added", {
|
|
474
639
|
orgId,
|
|
475
640
|
downloadId,
|
|
476
|
-
assetId: input.assetId
|
|
641
|
+
assetId: input.assetId,
|
|
642
|
+
actorId: actor.id
|
|
477
643
|
});
|
|
478
644
|
return this.downloadFiles(orgId, assets);
|
|
479
645
|
}
|
|
480
|
-
async removeDownloadAsset(
|
|
646
|
+
async removeDownloadAsset(downloadId, assetId, actor) {
|
|
647
|
+
this.require(actor, "downloads:write", downloadId);
|
|
648
|
+
const orgId = actor.orgId;
|
|
481
649
|
const assets = await this.uow.run(async ({ content, outbox }) => {
|
|
482
650
|
const assets = await content.removeDownloadAsset(orgId, downloadId, assetId);
|
|
483
651
|
await outbox.append([contentEvents.downloadAssetRemoved.make({
|
|
@@ -492,11 +660,14 @@ var ContentService = class {
|
|
|
492
660
|
this.logger.info("download asset removed", {
|
|
493
661
|
orgId,
|
|
494
662
|
downloadId,
|
|
495
|
-
assetId
|
|
663
|
+
assetId,
|
|
664
|
+
actorId: actor.id
|
|
496
665
|
});
|
|
497
666
|
return this.downloadFiles(orgId, assets);
|
|
498
667
|
}
|
|
499
|
-
async renameDownloadAsset(
|
|
668
|
+
async renameDownloadAsset(downloadId, assetId, displayName, actor) {
|
|
669
|
+
this.require(actor, "downloads:write", downloadId);
|
|
670
|
+
const orgId = actor.orgId;
|
|
500
671
|
const assets = await this.uow.run(async ({ content, outbox }) => {
|
|
501
672
|
const assets = await content.renameDownloadAsset(orgId, downloadId, assetId, displayName);
|
|
502
673
|
await outbox.append([contentEvents.downloadAssetRenamed.make({
|
|
@@ -511,13 +682,16 @@ var ContentService = class {
|
|
|
511
682
|
this.logger.info("download asset renamed", {
|
|
512
683
|
orgId,
|
|
513
684
|
downloadId,
|
|
514
|
-
assetId
|
|
685
|
+
assetId,
|
|
686
|
+
actorId: actor.id
|
|
515
687
|
});
|
|
516
688
|
return this.downloadFiles(orgId, assets);
|
|
517
689
|
}
|
|
518
690
|
/** The caller must send the COMPLETE ordered set — a partial list would
|
|
519
691
|
* silently unlink whatever it omitted. */
|
|
520
|
-
async reorderDownloadAssets(
|
|
692
|
+
async reorderDownloadAssets(downloadId, assetIds, actor) {
|
|
693
|
+
this.require(actor, "downloads:write", downloadId);
|
|
694
|
+
const orgId = actor.orgId;
|
|
521
695
|
const assets = await this.uow.run(async ({ content, outbox }) => {
|
|
522
696
|
const current = await content.listDownloadAssets(orgId, downloadId);
|
|
523
697
|
const currentIds = new Set(current.map((a) => a.assetId));
|
|
@@ -547,13 +721,330 @@ var ContentService = class {
|
|
|
547
721
|
});
|
|
548
722
|
return this.downloadFiles(orgId, assets);
|
|
549
723
|
}
|
|
550
|
-
|
|
551
|
-
|
|
724
|
+
async listEvents(query, actor) {
|
|
725
|
+
const ids = this.visibleIds(actor, ["events:read"], "events");
|
|
726
|
+
return this.pageWithThumbnails(await this.repo.listEvents(actor.orgId, query, ids));
|
|
727
|
+
}
|
|
728
|
+
async getEvent(id, actor) {
|
|
729
|
+
const event = await this.findEvent(id, actor);
|
|
730
|
+
if (!event) throw new NotFoundError("Event", id);
|
|
731
|
+
return event;
|
|
732
|
+
}
|
|
733
|
+
async findEvent(id, actor) {
|
|
734
|
+
this.require(actor, "events:read", id);
|
|
735
|
+
const event = await this.repo.findEventById(actor.orgId, id);
|
|
736
|
+
return event ? this.withThumbnail(event) : null;
|
|
737
|
+
}
|
|
738
|
+
async findEvents(ids, actor) {
|
|
739
|
+
for (const id of ids) this.require(actor, "events:read", id);
|
|
740
|
+
if (ids.length === 0) return [];
|
|
741
|
+
return (await this.repo.findEventsByIds(actor.orgId, ids)).map((event) => this.withThumbnail(event));
|
|
742
|
+
}
|
|
743
|
+
async createEvent(input, actor) {
|
|
744
|
+
this.require(actor, "events:write");
|
|
745
|
+
assertEventTimes(input.startsAt, input.endsAt);
|
|
746
|
+
const orgId = actor.orgId;
|
|
747
|
+
const { settings, ...fields } = input;
|
|
748
|
+
const event = await this.uow.run(async ({ content, outbox }) => {
|
|
749
|
+
const created = await content.createEvent(orgId, fields);
|
|
750
|
+
const event = settings ? {
|
|
751
|
+
...created,
|
|
752
|
+
settings: await content.patchEventSettings(orgId, created.id, settings)
|
|
753
|
+
} : created;
|
|
754
|
+
await outbox.append([contentEvents.eventCreated.make({
|
|
755
|
+
orgId,
|
|
756
|
+
data: event,
|
|
757
|
+
metadata: actorMetadata(actor)
|
|
758
|
+
})]);
|
|
759
|
+
return event;
|
|
760
|
+
});
|
|
761
|
+
this.logger.info("event created", {
|
|
762
|
+
orgId,
|
|
763
|
+
eventId: event.id,
|
|
764
|
+
title: event.title,
|
|
765
|
+
actorId: actor.id
|
|
766
|
+
});
|
|
767
|
+
return this.withThumbnail(event);
|
|
768
|
+
}
|
|
769
|
+
async updateEvent(id, patch, actor) {
|
|
770
|
+
this.require(actor, "events:write", id);
|
|
771
|
+
const orgId = actor.orgId;
|
|
772
|
+
await this.assertAttachable(actor, id, [patch.thumbnailAssetId], () => this.eventAssetIds(orgId, id), "An event");
|
|
773
|
+
const { settings, ...fields } = patch;
|
|
774
|
+
const { previous, event } = await this.uow.run(async ({ content, outbox }) => {
|
|
775
|
+
const previous = await content.findEventById(orgId, id);
|
|
776
|
+
if (!previous) {
|
|
777
|
+
this.logger.warn("event update rejected — not found", {
|
|
778
|
+
orgId,
|
|
779
|
+
eventId: id
|
|
780
|
+
});
|
|
781
|
+
throw new NotFoundError("Event", id);
|
|
782
|
+
}
|
|
783
|
+
if (previous.status === "canceled") {
|
|
784
|
+
this.logger.warn("event update rejected — canceled", {
|
|
785
|
+
orgId,
|
|
786
|
+
eventId: id
|
|
787
|
+
});
|
|
788
|
+
throw new ConflictError("The event is canceled", "canceled");
|
|
789
|
+
}
|
|
790
|
+
assertEventTimes(fields.startsAt ?? previous.startsAt, fields.endsAt ?? previous.endsAt);
|
|
791
|
+
const updated = await content.updateEvent(orgId, id, fields);
|
|
792
|
+
if (!updated) throw new NotFoundError("Event", id);
|
|
793
|
+
const event = settings ? {
|
|
794
|
+
...updated,
|
|
795
|
+
settings: await content.patchEventSettings(orgId, id, settings)
|
|
796
|
+
} : updated;
|
|
797
|
+
await outbox.append([contentEvents.eventUpdated.make({
|
|
798
|
+
orgId,
|
|
799
|
+
data: event,
|
|
800
|
+
metadata: eventChange(actor, previous, event)
|
|
801
|
+
})]);
|
|
802
|
+
return {
|
|
803
|
+
previous,
|
|
804
|
+
event
|
|
805
|
+
};
|
|
806
|
+
});
|
|
807
|
+
this.logger.info("event updated", {
|
|
808
|
+
orgId,
|
|
809
|
+
eventId: id,
|
|
810
|
+
fields: Object.keys(patch),
|
|
811
|
+
actorId: actor.id
|
|
812
|
+
});
|
|
813
|
+
await this.scheduleStartingSoon(orgId, previous, event);
|
|
814
|
+
await this.syncThumbnail(event);
|
|
815
|
+
return this.withThumbnail(event);
|
|
816
|
+
}
|
|
817
|
+
async cancelEvent(id, actor) {
|
|
818
|
+
this.require(actor, "events:write", id);
|
|
819
|
+
const orgId = actor.orgId;
|
|
820
|
+
const event = await this.uow.run(async ({ content, outbox }) => {
|
|
821
|
+
const previous = await content.findEventById(orgId, id);
|
|
822
|
+
if (!previous) {
|
|
823
|
+
this.logger.warn("event cancel rejected — not found", {
|
|
824
|
+
orgId,
|
|
825
|
+
eventId: id
|
|
826
|
+
});
|
|
827
|
+
throw new NotFoundError("Event", id);
|
|
828
|
+
}
|
|
829
|
+
if (previous.status === "canceled") {
|
|
830
|
+
this.logger.warn("event cancel rejected — already canceled", {
|
|
831
|
+
orgId,
|
|
832
|
+
eventId: id
|
|
833
|
+
});
|
|
834
|
+
throw new ConflictError("The event is already canceled", "canceled");
|
|
835
|
+
}
|
|
836
|
+
const updated = await content.updateEvent(orgId, id, { status: "canceled" });
|
|
837
|
+
if (!updated) throw new NotFoundError("Event", id);
|
|
838
|
+
await outbox.append([contentEvents.eventUpdated.make({
|
|
839
|
+
orgId,
|
|
840
|
+
data: updated,
|
|
841
|
+
metadata: eventChange(actor, previous, updated)
|
|
842
|
+
})]);
|
|
843
|
+
return updated;
|
|
844
|
+
});
|
|
845
|
+
this.logger.info("event canceled", {
|
|
846
|
+
orgId,
|
|
847
|
+
eventId: id,
|
|
848
|
+
actorId: actor.id
|
|
849
|
+
});
|
|
850
|
+
await this.cancelStartingSoon(orgId, id);
|
|
851
|
+
await this.syncThumbnail(event);
|
|
852
|
+
return this.withThumbnail(event);
|
|
853
|
+
}
|
|
854
|
+
async deleteEvent(id, actor) {
|
|
855
|
+
this.require(actor, "events:delete", id);
|
|
856
|
+
const orgId = actor.orgId;
|
|
857
|
+
await this.uow.run(async ({ content, outbox }) => {
|
|
858
|
+
const event = await content.findEventById(orgId, id);
|
|
859
|
+
if (!event) {
|
|
860
|
+
this.logger.warn("event delete rejected — not found", {
|
|
861
|
+
orgId,
|
|
862
|
+
eventId: id
|
|
863
|
+
});
|
|
864
|
+
throw new NotFoundError("Event", id);
|
|
865
|
+
}
|
|
866
|
+
const linked = await content.spacesLinkingContent(orgId, id);
|
|
867
|
+
if (!await content.deleteEvent(orgId, id)) throw new NotFoundError("Event", id);
|
|
868
|
+
await content.deleteEventSettings(orgId, id);
|
|
869
|
+
await outbox.append([contentEvents.eventDeleted.make({
|
|
870
|
+
orgId,
|
|
871
|
+
data: event,
|
|
872
|
+
metadata: actorMetadata(actor)
|
|
873
|
+
}), ...await this.spaceUpdates(content, orgId, linked, actor)]);
|
|
874
|
+
});
|
|
875
|
+
this.logger.info("event deleted", {
|
|
876
|
+
orgId,
|
|
877
|
+
eventId: id,
|
|
878
|
+
actorId: actor.id
|
|
879
|
+
});
|
|
880
|
+
await this.withdrawThumbnail(orgId, id);
|
|
881
|
+
await this.cancelStartingSoon(orgId, id);
|
|
552
882
|
}
|
|
553
|
-
|
|
554
|
-
|
|
883
|
+
async startingSoon(orgId, eventId, lead, now) {
|
|
884
|
+
const event = await this.repo.findEventById(orgId, eventId);
|
|
885
|
+
if (!event || event.status !== "published") {
|
|
886
|
+
this.logger.debug("event no longer published", {
|
|
887
|
+
orgId,
|
|
888
|
+
eventId,
|
|
889
|
+
lead
|
|
890
|
+
});
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const due = new Date(event.startsAt.getTime() - EVENT_LEADS[lead]);
|
|
894
|
+
if (due > now) {
|
|
895
|
+
await this.scheduler.at(EVENT_STARTING_SOON_JOB, due, startingSoonKey(eventId, lead), {
|
|
896
|
+
orgId,
|
|
897
|
+
eventId,
|
|
898
|
+
lead
|
|
899
|
+
});
|
|
900
|
+
this.logger.info("event starting soon timer moved", {
|
|
901
|
+
orgId,
|
|
902
|
+
eventId,
|
|
903
|
+
lead,
|
|
904
|
+
due: due.toISOString()
|
|
905
|
+
});
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
await this.uow.run(({ outbox }) => outbox.append([contentEvents.eventStartingSoon.make({
|
|
909
|
+
orgId,
|
|
910
|
+
data: {
|
|
911
|
+
eventId,
|
|
912
|
+
lead,
|
|
913
|
+
startsAt: event.startsAt
|
|
914
|
+
}
|
|
915
|
+
})]));
|
|
916
|
+
this.logger.info("event starting soon", {
|
|
917
|
+
orgId,
|
|
918
|
+
eventId,
|
|
919
|
+
lead
|
|
920
|
+
});
|
|
555
921
|
}
|
|
556
|
-
async
|
|
922
|
+
async scheduleStartingSoon(orgId, previous, event) {
|
|
923
|
+
if (event.status !== "published") {
|
|
924
|
+
if (previous.status === "published") await this.cancelStartingSoon(orgId, event.id);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (previous.status === "published" && previous.startsAt.getTime() === event.startsAt.getTime()) return;
|
|
928
|
+
const now = Date.now();
|
|
929
|
+
for (const lead of EVENT_LEAD_KEYS) {
|
|
930
|
+
const key = startingSoonKey(event.id, lead);
|
|
931
|
+
const runAt = new Date(event.startsAt.getTime() - EVENT_LEADS[lead]);
|
|
932
|
+
try {
|
|
933
|
+
if (runAt.getTime() > now) await this.scheduler.at(EVENT_STARTING_SOON_JOB, runAt, key, {
|
|
934
|
+
orgId,
|
|
935
|
+
eventId: event.id,
|
|
936
|
+
lead
|
|
937
|
+
});
|
|
938
|
+
else await this.scheduler.cancel(EVENT_STARTING_SOON_JOB, key);
|
|
939
|
+
} catch (err) {
|
|
940
|
+
this.logger.error("event starting soon timer not set", {
|
|
941
|
+
orgId,
|
|
942
|
+
eventId: event.id,
|
|
943
|
+
lead,
|
|
944
|
+
err
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async cancelStartingSoon(orgId, eventId) {
|
|
950
|
+
for (const lead of EVENT_LEAD_KEYS) try {
|
|
951
|
+
await this.scheduler.cancel(EVENT_STARTING_SOON_JOB, startingSoonKey(eventId, lead));
|
|
952
|
+
} catch (err) {
|
|
953
|
+
this.logger.warn("event starting soon timer not cancelled", {
|
|
954
|
+
orgId,
|
|
955
|
+
eventId,
|
|
956
|
+
lead,
|
|
957
|
+
err
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
async listEventAssets(eventId, actor) {
|
|
962
|
+
this.require(actor, "events:read", eventId);
|
|
963
|
+
const orgId = actor.orgId;
|
|
964
|
+
if (!await this.repo.findEventById(orgId, eventId)) throw new NotFoundError("Event", eventId);
|
|
965
|
+
return this.eventFiles(orgId, await this.repo.listEventAssets(orgId, eventId));
|
|
966
|
+
}
|
|
967
|
+
async eventFilesFor(eventIds, actor) {
|
|
968
|
+
for (const id of eventIds) this.require(actor, "events:read", id);
|
|
969
|
+
const byEvent = Object.fromEntries(eventIds.map((id) => [id, []]));
|
|
970
|
+
if (eventIds.length === 0) return byEvent;
|
|
971
|
+
const files = await this.eventFiles(actor.orgId, await this.repo.listEventAssetsFor(actor.orgId, eventIds));
|
|
972
|
+
for (const file of files) byEvent[file.eventId]?.push(file);
|
|
973
|
+
return byEvent;
|
|
974
|
+
}
|
|
975
|
+
async addEventAsset(eventId, input, actor) {
|
|
976
|
+
this.require(actor, "events:write", eventId);
|
|
977
|
+
const orgId = actor.orgId;
|
|
978
|
+
await this.assertAttachable(actor, eventId, [input.assetId], () => this.eventAssetIds(orgId, eventId), "An event");
|
|
979
|
+
const assets = await this.uow.run(({ content }) => content.addEventAsset(orgId, eventId, input));
|
|
980
|
+
this.logger.info("event asset added", {
|
|
981
|
+
orgId,
|
|
982
|
+
eventId,
|
|
983
|
+
assetId: input.assetId,
|
|
984
|
+
actorId: actor.id
|
|
985
|
+
});
|
|
986
|
+
return this.eventFiles(orgId, assets);
|
|
987
|
+
}
|
|
988
|
+
async removeEventAsset(eventId, assetId, actor) {
|
|
989
|
+
this.require(actor, "events:write", eventId);
|
|
990
|
+
const orgId = actor.orgId;
|
|
991
|
+
const assets = await this.uow.run(({ content }) => content.removeEventAsset(orgId, eventId, assetId));
|
|
992
|
+
this.logger.info("event asset removed", {
|
|
993
|
+
orgId,
|
|
994
|
+
eventId,
|
|
995
|
+
assetId,
|
|
996
|
+
actorId: actor.id
|
|
997
|
+
});
|
|
998
|
+
return this.eventFiles(orgId, assets);
|
|
999
|
+
}
|
|
1000
|
+
async renameEventAsset(eventId, assetId, displayName, actor) {
|
|
1001
|
+
this.require(actor, "events:write", eventId);
|
|
1002
|
+
const orgId = actor.orgId;
|
|
1003
|
+
const assets = await this.uow.run(({ content }) => content.renameEventAsset(orgId, eventId, assetId, displayName));
|
|
1004
|
+
this.logger.info("event asset renamed", {
|
|
1005
|
+
orgId,
|
|
1006
|
+
eventId,
|
|
1007
|
+
assetId,
|
|
1008
|
+
actorId: actor.id
|
|
1009
|
+
});
|
|
1010
|
+
return this.eventFiles(orgId, assets);
|
|
1011
|
+
}
|
|
1012
|
+
async reorderEventAssets(eventId, assetIds, actor) {
|
|
1013
|
+
this.require(actor, "events:write", eventId);
|
|
1014
|
+
const orgId = actor.orgId;
|
|
1015
|
+
const assets = await this.uow.run(async ({ content }) => {
|
|
1016
|
+
if (!sameIdSet((await content.listEventAssets(orgId, eventId)).map((link) => link.assetId), assetIds)) {
|
|
1017
|
+
this.logger.warn("event asset reorder rejected — id set mismatch", {
|
|
1018
|
+
orgId,
|
|
1019
|
+
eventId,
|
|
1020
|
+
assetIds
|
|
1021
|
+
});
|
|
1022
|
+
throw new ConflictError("Ordered asset ids does not match the event's current assets");
|
|
1023
|
+
}
|
|
1024
|
+
return content.reorderEventAssets(orgId, eventId, assetIds);
|
|
1025
|
+
});
|
|
1026
|
+
this.logger.debug("event assets reordered", {
|
|
1027
|
+
orgId,
|
|
1028
|
+
eventId
|
|
1029
|
+
});
|
|
1030
|
+
return this.eventFiles(orgId, assets);
|
|
1031
|
+
}
|
|
1032
|
+
async listBundles(query, actor) {
|
|
1033
|
+
this.require(actor, "bundles:read");
|
|
1034
|
+
return this.repo.listBundles(actor.orgId, query);
|
|
1035
|
+
}
|
|
1036
|
+
async getBundle(id, actor) {
|
|
1037
|
+
const bundle = await this.findBundle(id, actor);
|
|
1038
|
+
if (!bundle) throw new NotFoundError("Bundle", id);
|
|
1039
|
+
return bundle;
|
|
1040
|
+
}
|
|
1041
|
+
async findBundle(id, actor) {
|
|
1042
|
+
this.require(actor, "bundles:read");
|
|
1043
|
+
return this.repo.findBundleById(actor.orgId, id);
|
|
1044
|
+
}
|
|
1045
|
+
async createBundle(input, actor) {
|
|
1046
|
+
this.require(actor, "bundles:write");
|
|
1047
|
+
const orgId = actor.orgId;
|
|
557
1048
|
const { bundle, items } = await this.uow.run(async ({ content, outbox }) => {
|
|
558
1049
|
const created = await content.createBundle(orgId, input);
|
|
559
1050
|
const initial = input.contentIds?.length ? await content.listBundleItems(orgId, created.id) : [];
|
|
@@ -576,11 +1067,14 @@ var ContentService = class {
|
|
|
576
1067
|
orgId,
|
|
577
1068
|
bundleId: bundle.id,
|
|
578
1069
|
name: bundle.name,
|
|
579
|
-
itemCount: items.length
|
|
1070
|
+
itemCount: items.length,
|
|
1071
|
+
actorId: actor.id
|
|
580
1072
|
});
|
|
581
1073
|
return bundle;
|
|
582
1074
|
}
|
|
583
|
-
async updateBundle(
|
|
1075
|
+
async updateBundle(id, patch, actor) {
|
|
1076
|
+
this.require(actor, "bundles:write");
|
|
1077
|
+
const orgId = actor.orgId;
|
|
584
1078
|
const bundle = await this.uow.run(async ({ content, outbox }) => {
|
|
585
1079
|
const updated = await content.updateBundle(orgId, id, patch);
|
|
586
1080
|
if (!updated) {
|
|
@@ -599,11 +1093,14 @@ var ContentService = class {
|
|
|
599
1093
|
this.logger.info("bundle updated", {
|
|
600
1094
|
orgId,
|
|
601
1095
|
bundleId: id,
|
|
602
|
-
fields: Object.keys(patch)
|
|
1096
|
+
fields: Object.keys(patch),
|
|
1097
|
+
actorId: actor.id
|
|
603
1098
|
});
|
|
604
1099
|
return bundle;
|
|
605
1100
|
}
|
|
606
|
-
async deleteBundle(
|
|
1101
|
+
async deleteBundle(id, actor) {
|
|
1102
|
+
this.require(actor, "bundles:delete");
|
|
1103
|
+
const orgId = actor.orgId;
|
|
607
1104
|
await this.uow.run(async ({ content, outbox }) => {
|
|
608
1105
|
const bundle = await content.findBundleById(orgId, id);
|
|
609
1106
|
if (!bundle) {
|
|
@@ -621,13 +1118,17 @@ var ContentService = class {
|
|
|
621
1118
|
});
|
|
622
1119
|
this.logger.info("bundle deleted", {
|
|
623
1120
|
orgId,
|
|
624
|
-
bundleId: id
|
|
1121
|
+
bundleId: id,
|
|
1122
|
+
actorId: actor.id
|
|
625
1123
|
});
|
|
626
1124
|
}
|
|
627
|
-
listBundleItems(
|
|
628
|
-
|
|
1125
|
+
async listBundleItems(bundleId, actor) {
|
|
1126
|
+
this.require(actor, "bundles:read");
|
|
1127
|
+
return this.repo.listBundleItems(actor.orgId, bundleId);
|
|
629
1128
|
}
|
|
630
|
-
async addBundleItem(
|
|
1129
|
+
async addBundleItem(bundleId, contentId, actor) {
|
|
1130
|
+
this.require(actor, "bundles:write");
|
|
1131
|
+
const orgId = actor.orgId;
|
|
631
1132
|
const items = await this.uow.run(async ({ content, outbox }) => {
|
|
632
1133
|
const all = await content.addBundleItem(orgId, bundleId, contentId);
|
|
633
1134
|
await outbox.append([contentEvents.bundleItemAdded.make({
|
|
@@ -642,11 +1143,14 @@ var ContentService = class {
|
|
|
642
1143
|
this.logger.info("bundle item added", {
|
|
643
1144
|
orgId,
|
|
644
1145
|
bundleId,
|
|
645
|
-
contentId
|
|
1146
|
+
contentId,
|
|
1147
|
+
actorId: actor.id
|
|
646
1148
|
});
|
|
647
1149
|
return items;
|
|
648
1150
|
}
|
|
649
|
-
async removeBundleItem(
|
|
1151
|
+
async removeBundleItem(bundleId, contentId, actor) {
|
|
1152
|
+
this.require(actor, "bundles:write");
|
|
1153
|
+
const orgId = actor.orgId;
|
|
650
1154
|
const items = await this.uow.run(async ({ content, outbox }) => {
|
|
651
1155
|
const all = await content.removeBundleItem(orgId, bundleId, contentId);
|
|
652
1156
|
await outbox.append([contentEvents.bundleItemRemoved.make({
|
|
@@ -661,7 +1165,8 @@ var ContentService = class {
|
|
|
661
1165
|
this.logger.info("bundle item removed", {
|
|
662
1166
|
orgId,
|
|
663
1167
|
bundleId,
|
|
664
|
-
contentId
|
|
1168
|
+
contentId,
|
|
1169
|
+
actorId: actor.id
|
|
665
1170
|
});
|
|
666
1171
|
return items;
|
|
667
1172
|
}
|
|
@@ -694,35 +1199,58 @@ var ContentService = class {
|
|
|
694
1199
|
settings
|
|
695
1200
|
};
|
|
696
1201
|
}
|
|
697
|
-
async listCommunities(
|
|
698
|
-
|
|
1202
|
+
async listCommunities(query, actor) {
|
|
1203
|
+
const ids = this.visibleIds(actor, COMMUNITY_READERS, "communities");
|
|
1204
|
+
return this.pageWithThumbnails(await this.repo.listCommunities(actor.orgId, query, ids));
|
|
699
1205
|
}
|
|
700
|
-
async getCommunity(
|
|
701
|
-
const community = await this.
|
|
1206
|
+
async getCommunity(id, actor) {
|
|
1207
|
+
const community = await this.findCommunity(id, actor);
|
|
1208
|
+
if (!community) throw new NotFoundError("Community", id);
|
|
1209
|
+
return community;
|
|
1210
|
+
}
|
|
1211
|
+
async findCommunity(id, actor) {
|
|
1212
|
+
this.requireAny(actor, COMMUNITY_READERS, id);
|
|
1213
|
+
const community = await this.repo.findCommunityById(actor.orgId, id);
|
|
702
1214
|
return community ? this.withThumbnail(community) : null;
|
|
703
1215
|
}
|
|
704
1216
|
communitiesWithDigest() {
|
|
705
1217
|
return this.repo.communitiesWithDigest();
|
|
706
1218
|
}
|
|
707
|
-
listCommunitiesById(
|
|
708
|
-
|
|
1219
|
+
async listCommunitiesById(ids, actor) {
|
|
1220
|
+
if (!actor.permissions.anywhere("communities:read")) {
|
|
1221
|
+
this.logger.warn("content list refused", {
|
|
1222
|
+
orgId: actor.orgId,
|
|
1223
|
+
actorId: actor.id,
|
|
1224
|
+
permission: "communities:read"
|
|
1225
|
+
});
|
|
1226
|
+
throw new ForbiddenError("You cannot see communities");
|
|
1227
|
+
}
|
|
1228
|
+
const visible = ids.filter((id) => actor.permissions.has("communities:read", id));
|
|
1229
|
+
return visible.length ? this.repo.findCommunitiesByIds(actor.orgId, visible) : [];
|
|
709
1230
|
}
|
|
710
|
-
getSpace(
|
|
711
|
-
|
|
1231
|
+
async getSpace(spaceId, actor) {
|
|
1232
|
+
const space = await this.repo.findSpace(actor.orgId, spaceId);
|
|
1233
|
+
if (space) this.require(actor, "communities:read", space.communityId);
|
|
1234
|
+
return space;
|
|
712
1235
|
}
|
|
713
|
-
async getCommunitySpace(
|
|
714
|
-
|
|
715
|
-
|
|
1236
|
+
async getCommunitySpace(communityId, spaceId, actor) {
|
|
1237
|
+
this.require(actor, "communities:read", communityId);
|
|
1238
|
+
const space = await this.repo.findSpace(actor.orgId, spaceId);
|
|
1239
|
+
if (space?.communityId !== communityId) throw new NotFoundError("Space", spaceId);
|
|
1240
|
+
return space;
|
|
716
1241
|
}
|
|
717
|
-
async listSpaces(
|
|
718
|
-
|
|
719
|
-
|
|
1242
|
+
async listSpaces(communityId, actor) {
|
|
1243
|
+
this.requireAny(actor, COMMUNITY_READERS, communityId);
|
|
1244
|
+
if (!await this.repo.findCommunityById(actor.orgId, communityId)) throw new NotFoundError("Community", communityId);
|
|
1245
|
+
return this.repo.listSpaces(actor.orgId, communityId);
|
|
720
1246
|
}
|
|
721
|
-
async getDmSettings(
|
|
722
|
-
|
|
1247
|
+
async getDmSettings(communityId, actor) {
|
|
1248
|
+
this.require(actor, "communities:read", communityId);
|
|
1249
|
+
return (await this.repo.findCommunityById(actor.orgId, communityId))?.settings.dm ?? null;
|
|
723
1250
|
}
|
|
724
|
-
async createCommunity(
|
|
725
|
-
|
|
1251
|
+
async createCommunity(input, actor) {
|
|
1252
|
+
this.require(actor, "communities:write");
|
|
1253
|
+
const orgId = actor.orgId;
|
|
726
1254
|
const community = await this.uow.run(async ({ content, outbox }) => {
|
|
727
1255
|
const created = await content.createCommunity(orgId, input, slugify(input.title));
|
|
728
1256
|
await outbox.append([contentEvents.communityCreated.make({
|
|
@@ -739,9 +1267,10 @@ var ContentService = class {
|
|
|
739
1267
|
});
|
|
740
1268
|
return this.withThumbnail(community);
|
|
741
1269
|
}
|
|
742
|
-
async updateCommunity(
|
|
743
|
-
|
|
744
|
-
|
|
1270
|
+
async updateCommunity(id, patch, actor) {
|
|
1271
|
+
this.require(actor, "communities:write", id);
|
|
1272
|
+
const orgId = actor.orgId;
|
|
1273
|
+
await this.assertAttachable(actor, id, [patch.thumbnailAssetId], async () => [(await this.repo.findCommunityById(orgId, id))?.thumbnailAssetId ?? null], "A community");
|
|
745
1274
|
const community = await this.uow.run(async ({ content, outbox }) => {
|
|
746
1275
|
const updated = await content.updateCommunity(orgId, id, patch);
|
|
747
1276
|
if (!updated) {
|
|
@@ -764,16 +1293,18 @@ var ContentService = class {
|
|
|
764
1293
|
communityId: id,
|
|
765
1294
|
actorId: actor.id
|
|
766
1295
|
});
|
|
1296
|
+
await this.syncThumbnail(community);
|
|
767
1297
|
return this.withThumbnail(community);
|
|
768
1298
|
}
|
|
769
|
-
publishCommunity(
|
|
770
|
-
return this.updateCommunity(
|
|
1299
|
+
publishCommunity(id, actor) {
|
|
1300
|
+
return this.updateCommunity(id, { status: "published" }, actor);
|
|
771
1301
|
}
|
|
772
|
-
unpublishCommunity(
|
|
773
|
-
return this.updateCommunity(
|
|
1302
|
+
unpublishCommunity(id, actor) {
|
|
1303
|
+
return this.updateCommunity(id, { status: "draft" }, actor);
|
|
774
1304
|
}
|
|
775
|
-
async deleteCommunity(
|
|
776
|
-
|
|
1305
|
+
async deleteCommunity(id, actor) {
|
|
1306
|
+
this.require(actor, "communities:delete", id);
|
|
1307
|
+
const orgId = actor.orgId;
|
|
777
1308
|
await this.uow.run(async ({ content, outbox }) => {
|
|
778
1309
|
const community = await content.findCommunityById(orgId, id);
|
|
779
1310
|
if (!community) {
|
|
@@ -798,9 +1329,11 @@ var ContentService = class {
|
|
|
798
1329
|
communityId: id,
|
|
799
1330
|
actorId: actor.id
|
|
800
1331
|
});
|
|
1332
|
+
await this.withdrawThumbnail(orgId, id);
|
|
801
1333
|
}
|
|
802
|
-
async patchCommunitySettings(
|
|
803
|
-
|
|
1334
|
+
async patchCommunitySettings(id, patch, actor) {
|
|
1335
|
+
this.require(actor, "communities:write", id);
|
|
1336
|
+
const orgId = actor.orgId;
|
|
804
1337
|
const community = await this.uow.run(async ({ content, outbox }) => {
|
|
805
1338
|
const updated = await content.patchCommunitySettings(orgId, id, patch);
|
|
806
1339
|
if (!updated) {
|
|
@@ -825,8 +1358,9 @@ var ContentService = class {
|
|
|
825
1358
|
});
|
|
826
1359
|
return this.withThumbnail(community);
|
|
827
1360
|
}
|
|
828
|
-
async createSpace(
|
|
829
|
-
|
|
1361
|
+
async createSpace(communityId, input, actor) {
|
|
1362
|
+
this.require(actor, "communities:write", communityId);
|
|
1363
|
+
const orgId = actor.orgId;
|
|
830
1364
|
const resolved = this.resolvedSpace(input);
|
|
831
1365
|
const space = await this.uow.run(async ({ content, outbox }) => {
|
|
832
1366
|
if (!await content.findCommunityById(orgId, communityId)) {
|
|
@@ -858,8 +1392,9 @@ var ContentService = class {
|
|
|
858
1392
|
});
|
|
859
1393
|
return space;
|
|
860
1394
|
}
|
|
861
|
-
async updateSpace(
|
|
862
|
-
|
|
1395
|
+
async updateSpace(communityId, spaceId, patch, actor) {
|
|
1396
|
+
this.require(actor, "communities:write", communityId);
|
|
1397
|
+
const orgId = actor.orgId;
|
|
863
1398
|
const space = await this.uow.run(async ({ content, outbox }) => {
|
|
864
1399
|
const current = await content.findSpace(orgId, spaceId);
|
|
865
1400
|
if (!current || current.communityId !== communityId) {
|
|
@@ -894,8 +1429,9 @@ var ContentService = class {
|
|
|
894
1429
|
});
|
|
895
1430
|
return space;
|
|
896
1431
|
}
|
|
897
|
-
async deleteSpace(
|
|
898
|
-
|
|
1432
|
+
async deleteSpace(communityId, spaceId, actor) {
|
|
1433
|
+
this.require(actor, "communities:write", communityId);
|
|
1434
|
+
const orgId = actor.orgId;
|
|
899
1435
|
await this.uow.run(async ({ content, outbox }) => {
|
|
900
1436
|
const space = await content.findSpace(orgId, spaceId);
|
|
901
1437
|
if (!space || space.communityId !== communityId) {
|
|
@@ -921,8 +1457,9 @@ var ContentService = class {
|
|
|
921
1457
|
actorId: actor.id
|
|
922
1458
|
});
|
|
923
1459
|
}
|
|
924
|
-
async reorderSpaces(
|
|
925
|
-
|
|
1460
|
+
async reorderSpaces(communityId, orderedIds, actor) {
|
|
1461
|
+
this.require(actor, "communities:write", communityId);
|
|
1462
|
+
const orgId = actor.orgId;
|
|
926
1463
|
const spaces = await this.uow.run(async ({ content, outbox }) => {
|
|
927
1464
|
if (!await content.findCommunityById(orgId, communityId)) {
|
|
928
1465
|
this.logger.warn("spaces reorder rejected — community not found", {
|
|
@@ -948,8 +1485,9 @@ var ContentService = class {
|
|
|
948
1485
|
});
|
|
949
1486
|
return spaces;
|
|
950
1487
|
}
|
|
951
|
-
async setSpaceTopics(
|
|
952
|
-
|
|
1488
|
+
async setSpaceTopics(communityId, spaceId, input, actor) {
|
|
1489
|
+
this.require(actor, "communities:write", communityId);
|
|
1490
|
+
const orgId = actor.orgId;
|
|
953
1491
|
const names = input.topics.map((topic) => topic.name);
|
|
954
1492
|
if (new Set(names).size !== names.length) throw new InvalidInputError("A space topic name is already used");
|
|
955
1493
|
const space = await this.uow.run(async ({ content, outbox }) => {
|
|
@@ -984,8 +1522,9 @@ var ContentService = class {
|
|
|
984
1522
|
});
|
|
985
1523
|
return space;
|
|
986
1524
|
}
|
|
987
|
-
async addSpaceLink(
|
|
988
|
-
|
|
1525
|
+
async addSpaceLink(communityId, spaceId, contentId, actor) {
|
|
1526
|
+
this.require(actor, "communities:write", communityId);
|
|
1527
|
+
const orgId = actor.orgId;
|
|
989
1528
|
const space = await this.uow.run(async ({ content, outbox }) => {
|
|
990
1529
|
const current = await content.findSpace(orgId, spaceId);
|
|
991
1530
|
if (!current || current.communityId !== communityId) {
|
|
@@ -1015,8 +1554,9 @@ var ContentService = class {
|
|
|
1015
1554
|
});
|
|
1016
1555
|
return space;
|
|
1017
1556
|
}
|
|
1018
|
-
async removeSpaceLink(
|
|
1019
|
-
|
|
1557
|
+
async removeSpaceLink(communityId, spaceId, linkId, actor) {
|
|
1558
|
+
this.require(actor, "communities:write", communityId);
|
|
1559
|
+
const orgId = actor.orgId;
|
|
1020
1560
|
const space = await this.uow.run(async ({ content, outbox }) => {
|
|
1021
1561
|
const current = await content.findSpace(orgId, spaceId);
|
|
1022
1562
|
if (!current || current.communityId !== communityId) {
|
|
@@ -1045,8 +1585,9 @@ var ContentService = class {
|
|
|
1045
1585
|
});
|
|
1046
1586
|
return space;
|
|
1047
1587
|
}
|
|
1048
|
-
async reorderSpaceLinks(
|
|
1049
|
-
|
|
1588
|
+
async reorderSpaceLinks(communityId, spaceId, orderedIds, actor) {
|
|
1589
|
+
this.require(actor, "communities:write", communityId);
|
|
1590
|
+
const orgId = actor.orgId;
|
|
1050
1591
|
const space = await this.uow.run(async ({ content, outbox }) => {
|
|
1051
1592
|
const current = await content.findSpace(orgId, spaceId);
|
|
1052
1593
|
if (!current || current.communityId !== communityId) {
|