@headless-lms/core 0.7.0 → 0.8.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/file-urls.d.ts +5 -19
- package/dist/assets/file-urls.js +2 -23
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +3 -3
- package/dist/assets/ports.d.ts +9 -10
- package/dist/assets/service.d.ts +10 -13
- package/dist/assets/service.js +41 -62
- package/dist/assets/service.js.map +1 -1
- package/dist/content/events.d.ts +13 -13
- package/dist/content/service.d.ts +4 -1
- package/dist/content/service.js +52 -29
- package/dist/content/service.js.map +1 -1
- package/dist/discussion/chats.d.ts +1 -2
- package/dist/discussion/chats.js +8 -17
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/ports.d.ts +0 -6
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +1 -4
- package/dist/discussion/posts.js +8 -21
- package/dist/discussion/posts.js.map +1 -1
- package/dist/entitlements/events.d.ts +33 -33
- package/dist/identity/events.d.ts +2 -2
- package/dist/integrations/events.d.ts +3 -3
- package/dist/organizations/events.d.ts +2 -2
- package/dist/reporting/member/ports.d.ts +1 -10
- package/dist/reporting/member/service.d.ts +2 -11
- package/dist/reporting/member/service.js +12 -62
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/types/ports.d.ts +4 -2
- package/dist/types/ports.js.map +1 -1
- package/dist/types/schemas/content.d.ts +4 -4
- package/dist/types/schemas/discussion.d.ts +14 -14
- package/dist/types/schemas/entitlements.d.ts +8 -8
- package/dist/types/schemas/notifications.d.ts +3 -3
- package/package.json +1 -1
|
@@ -22,9 +22,9 @@ declare const identityEvents: {
|
|
|
22
22
|
id: import("zod").ZodString;
|
|
23
23
|
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
24
24
|
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
25
|
+
email: import("zod").ZodEmail;
|
|
25
26
|
firstName: import("zod").ZodNullable<import("zod").ZodString>;
|
|
26
27
|
lastName: import("zod").ZodNullable<import("zod").ZodString>;
|
|
27
|
-
email: import("zod").ZodEmail;
|
|
28
28
|
}, import("zod/v4/core").$strip>>;
|
|
29
29
|
userUpdated: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
30
30
|
id: string;
|
|
@@ -46,9 +46,9 @@ declare const identityEvents: {
|
|
|
46
46
|
id: import("zod").ZodString;
|
|
47
47
|
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
48
48
|
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
49
|
+
email: import("zod").ZodEmail;
|
|
49
50
|
firstName: import("zod").ZodNullable<import("zod").ZodString>;
|
|
50
51
|
lastName: import("zod").ZodNullable<import("zod").ZodString>;
|
|
51
|
-
email: import("zod").ZodEmail;
|
|
52
52
|
}, import("zod/v4/core").$strip>>;
|
|
53
53
|
userDeleted: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
54
54
|
id: string;
|
|
@@ -26,9 +26,9 @@ declare const integrationEvents: {
|
|
|
26
26
|
id: import("zod").ZodString;
|
|
27
27
|
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
28
28
|
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
29
|
-
active: import("zod").ZodBoolean;
|
|
30
29
|
integrationId: import("zod").ZodString;
|
|
31
30
|
config: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodType<JsonValue, unknown, import("zod/v4/core").$ZodTypeInternals<JsonValue, unknown>>>;
|
|
31
|
+
active: import("zod").ZodBoolean;
|
|
32
32
|
}, import("zod/v4/core").$strip>>;
|
|
33
33
|
connectionUpdated: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
34
34
|
orgId: string;
|
|
@@ -53,9 +53,9 @@ declare const integrationEvents: {
|
|
|
53
53
|
id: import("zod").ZodString;
|
|
54
54
|
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
55
55
|
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
56
|
-
active: import("zod").ZodBoolean;
|
|
57
56
|
integrationId: import("zod").ZodString;
|
|
58
57
|
config: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodType<JsonValue, unknown, import("zod/v4/core").$ZodTypeInternals<JsonValue, unknown>>>;
|
|
58
|
+
active: import("zod").ZodBoolean;
|
|
59
59
|
}, import("zod/v4/core").$strip>>;
|
|
60
60
|
connectionRemoved: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
61
61
|
orgId: string;
|
|
@@ -80,9 +80,9 @@ declare const integrationEvents: {
|
|
|
80
80
|
id: import("zod").ZodString;
|
|
81
81
|
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
82
82
|
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
83
|
-
active: import("zod").ZodBoolean;
|
|
84
83
|
integrationId: import("zod").ZodString;
|
|
85
84
|
config: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodType<JsonValue, unknown, import("zod/v4/core").$ZodTypeInternals<JsonValue, unknown>>>;
|
|
85
|
+
active: import("zod").ZodBoolean;
|
|
86
86
|
}, import("zod/v4/core").$strip>>;
|
|
87
87
|
};
|
|
88
88
|
type ConnectionCreated = EventOf<typeof integrationEvents.connectionCreated>;
|
|
@@ -218,8 +218,8 @@ declare const organizationEvents: {
|
|
|
218
218
|
updatedAt: z.ZodCoercedDate<unknown>;
|
|
219
219
|
email: z.ZodEmail;
|
|
220
220
|
expiresAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
221
|
-
roleKey: z.ZodString;
|
|
222
221
|
invitedBy: z.ZodString;
|
|
222
|
+
roleKey: z.ZodString;
|
|
223
223
|
}, z.core.$strip>>;
|
|
224
224
|
inviteAccepted: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
225
225
|
id: string;
|
|
@@ -256,8 +256,8 @@ declare const organizationEvents: {
|
|
|
256
256
|
updatedAt: z.ZodCoercedDate<unknown>;
|
|
257
257
|
email: z.ZodEmail;
|
|
258
258
|
expiresAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
|
|
259
|
-
roleKey: z.ZodString;
|
|
260
259
|
invitedBy: z.ZodString;
|
|
260
|
+
roleKey: z.ZodString;
|
|
261
261
|
}, z.core.$strip>>;
|
|
262
262
|
inviteCanceled: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
263
263
|
id: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Page } from "../../types/shared.js";
|
|
2
|
-
import { ActivityView, CourseView, DownloadFile, DownloadView, EventAttendee, MemberEvent, MemberEventSummary, MemberEventsQuery, Module
|
|
2
|
+
import { ActivityView, CourseView, DownloadFile, DownloadView, EventAttendee, MemberEvent, MemberEventSummary, MemberEventsQuery, Module } from "../../types/schemas/content.js";
|
|
3
3
|
import { NotificationFilter } from "../../types/notifications.js";
|
|
4
4
|
import "../../types/index.js";
|
|
5
5
|
import { Actor } from "../../shared/actor.js";
|
|
@@ -21,10 +21,6 @@ interface MemberReportService {
|
|
|
21
21
|
download: DownloadView;
|
|
22
22
|
assets: DownloadFile[];
|
|
23
23
|
}>;
|
|
24
|
-
/** Signed URL of a course, download or community thumbnail: published, or the item's read
|
|
25
|
-
* permission. An event's when the caller can read the event. */
|
|
26
|
-
thumbnailFile(owner: 'course' | 'download' | 'community' | 'event', id: string, actor: Actor): Promise<string>;
|
|
27
|
-
spaceImageFile(spaceId: string, image: SpaceImage, actor: Actor): Promise<string>;
|
|
28
24
|
getEvent(eventId: string, actor: Actor): Promise<MemberEvent>;
|
|
29
25
|
listEvents(query: MemberEventsQuery, actor: Actor): Promise<Page<MemberEventSummary>>;
|
|
30
26
|
eventCalendar(eventId: string, actor: Actor): Promise<string>;
|
|
@@ -32,11 +28,6 @@ interface MemberReportService {
|
|
|
32
28
|
page: number;
|
|
33
29
|
pageSize: number;
|
|
34
30
|
}, actor: Actor): Promise<Page<EventAttendee>>;
|
|
35
|
-
eventFile(eventId: string, assetId: string, actor: Actor): Promise<string>;
|
|
36
|
-
/** Signed URL of an asset the activity links, for a person who can open its course. */
|
|
37
|
-
activityFile(activityId: string, assetId: string, actor: Actor): Promise<string>;
|
|
38
|
-
/** Signed download URL of a file in a download the person can open. */
|
|
39
|
-
downloadFile(downloadId: string, assetId: string, actor: Actor): Promise<string>;
|
|
40
31
|
directory(communityId: string, query: DirectoryQuery, actor: Actor): Promise<Page<DirectoryEntry>>;
|
|
41
32
|
communityHome(communityId: string, actor: Actor): Promise<CommunityHome>;
|
|
42
33
|
communitySpace(communityId: string, spaceId: string, actor: Actor): Promise<CommunityHomeSpace>;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Page } from "../../types/shared.js";
|
|
2
|
-
import { ActivityView, CourseView, DownloadFile, DownloadView, EventAttendee, MemberEvent, MemberEventSummary, MemberEventsQuery, Module
|
|
2
|
+
import { ActivityView, CourseView, DownloadFile, DownloadView, EventAttendee, MemberEvent, MemberEventSummary, MemberEventsQuery, Module } from "../../types/schemas/content.js";
|
|
3
3
|
import { NotificationFilter } from "../../types/notifications.js";
|
|
4
4
|
import "../../types/index.js";
|
|
5
5
|
import { Actor } from "../../shared/actor.js";
|
|
6
6
|
import { ChannelRegistry, NotificationCategoryService } from "../../shared/ports.js";
|
|
7
7
|
import { FileUrls } from "../../assets/file-urls.js";
|
|
8
|
-
import { AssetFiles } from "../../assets/ports.js";
|
|
9
8
|
import "../../assets/index.js";
|
|
10
9
|
import { ContentService, ItemTitles } from "../../content/ports.js";
|
|
11
10
|
import "../../content/index.js";
|
|
@@ -25,14 +24,12 @@ import { CommunityHome, CommunityHomeSpace, CommunityNotificationSettingsView, C
|
|
|
25
24
|
import { MemberReportRepository, MemberReportService } from "./ports.js";
|
|
26
25
|
//#region src/reporting/member/service.d.ts
|
|
27
26
|
type MemberAccess = Pick<EntitlementsService, 'isCommunityMember' | 'openSpaceIds' | 'spaceStandings' | 'hasAccess' | 'listAccessibleContent' | 'openCommunityIds' | 'opensEverySpace' | 'everySpaceOpeners'>;
|
|
28
|
-
type MemberContent = Pick<ContentService, 'findCourse' | 'listCourseModules' | 'listCourseActivityViews' | 'listCourseActivities' | '
|
|
27
|
+
type MemberContent = Pick<ContentService, 'findCourse' | 'listCourseModules' | 'listCourseActivityViews' | 'listCourseActivities' | 'findDownload' | 'listDownloadAssets' | 'findCommunity' | 'listSpaces' | 'getSpace' | 'findEvent' | 'findEvents' | 'eventFilesFor'>;
|
|
29
28
|
type MemberPeople = Pick<OrganizationService, 'systemActor' | 'permissionsOfMany' | 'holders'>;
|
|
30
|
-
type Owner = 'course' | 'download' | 'community' | 'event';
|
|
31
29
|
type MemberReportServiceParams = {
|
|
32
30
|
repo: MemberReportRepository;
|
|
33
31
|
content: MemberContent;
|
|
34
32
|
progress: Pick<ProgressService, 'get' | 'listByTargets'>;
|
|
35
|
-
files: Pick<AssetFiles, 'signFile'>;
|
|
36
33
|
fileUrls: FileUrls;
|
|
37
34
|
access: MemberAccess;
|
|
38
35
|
people: MemberPeople;
|
|
@@ -55,7 +52,6 @@ declare class MemberReportServiceImpl implements MemberReportService {
|
|
|
55
52
|
private readonly repo;
|
|
56
53
|
private readonly content;
|
|
57
54
|
private readonly progress;
|
|
58
|
-
private readonly files;
|
|
59
55
|
private readonly fileUrls;
|
|
60
56
|
private readonly access;
|
|
61
57
|
private readonly people;
|
|
@@ -93,10 +89,6 @@ declare class MemberReportServiceImpl implements MemberReportService {
|
|
|
93
89
|
download: DownloadView;
|
|
94
90
|
assets: DownloadFile[];
|
|
95
91
|
}>;
|
|
96
|
-
thumbnailFile(owner: Owner, id: string, actor: Actor): Promise<string>;
|
|
97
|
-
spaceImageFile(spaceId: string, image: SpaceImage, actor: Actor): Promise<string>;
|
|
98
|
-
activityFile(activityId: string, assetId: string, actor: Actor): Promise<string>;
|
|
99
|
-
downloadFile(downloadId: string, assetId: string, actor: Actor): Promise<string>;
|
|
100
92
|
private seeEvents;
|
|
101
93
|
private requireEvent;
|
|
102
94
|
private eventSummaries;
|
|
@@ -108,7 +100,6 @@ declare class MemberReportServiceImpl implements MemberReportService {
|
|
|
108
100
|
page: number;
|
|
109
101
|
pageSize: number;
|
|
110
102
|
}, actor: Actor): Promise<Page<EventAttendee>>;
|
|
111
|
-
eventFile(eventId: string, assetId: string, actor: Actor): Promise<string>;
|
|
112
103
|
directory(communityId: string, query: DirectoryQuery, actor: Actor): Promise<Page<DirectoryEntry>>;
|
|
113
104
|
communityHome(communityId: string, actor: Actor): Promise<CommunityHome>;
|
|
114
105
|
communitySpace(communityId: string, spaceId: string, actor: Actor): Promise<CommunityHomeSpace>;
|
|
@@ -33,23 +33,11 @@ const READ_OF = {
|
|
|
33
33
|
community: "communities:read",
|
|
34
34
|
event: "events:read"
|
|
35
35
|
};
|
|
36
|
-
const THUMBNAIL_OF = {
|
|
37
|
-
course: "Course thumbnail",
|
|
38
|
-
download: "Download thumbnail",
|
|
39
|
-
community: "Community thumbnail",
|
|
40
|
-
event: "Event thumbnail"
|
|
41
|
-
};
|
|
42
|
-
const SPACE_IMAGE_ASSET = {
|
|
43
|
-
cover: (space) => space.coverAssetId,
|
|
44
|
-
background: (space) => space.backgroundAssetId,
|
|
45
|
-
icon: (space) => space.iconAssetId
|
|
46
|
-
};
|
|
47
36
|
const EVENT_FACES = 10;
|
|
48
37
|
var MemberReportServiceImpl = class {
|
|
49
38
|
repo;
|
|
50
39
|
content;
|
|
51
40
|
progress;
|
|
52
|
-
files;
|
|
53
41
|
fileUrls;
|
|
54
42
|
access;
|
|
55
43
|
people;
|
|
@@ -65,7 +53,6 @@ var MemberReportServiceImpl = class {
|
|
|
65
53
|
this.repo = params.repo;
|
|
66
54
|
this.content = params.content;
|
|
67
55
|
this.progress = params.progress;
|
|
68
|
-
this.files = params.files;
|
|
69
56
|
this.fileUrls = params.fileUrls;
|
|
70
57
|
this.access = params.access;
|
|
71
58
|
this.people = params.people;
|
|
@@ -293,36 +280,6 @@ var MemberReportServiceImpl = class {
|
|
|
293
280
|
assets: await this.content.listDownloadAssets(downloadId, this.system(actor))
|
|
294
281
|
};
|
|
295
282
|
}
|
|
296
|
-
async thumbnailFile(owner, id, actor) {
|
|
297
|
-
const system = this.system(actor);
|
|
298
|
-
const item = owner === "event" ? (await this.seeEvents([id], actor))[0]?.event : owner === "course" ? await this.content.findCourse(id, system) : owner === "download" ? await this.content.findDownload(id, system) : await this.content.findCommunity(id, system);
|
|
299
|
-
if (!item?.thumbnailAssetId || owner !== "event" && item.status !== "published" && !actor.permissions.has(READ_OF[owner], id)) throw new NotFoundError(THUMBNAIL_OF[owner], id);
|
|
300
|
-
return signed(await this.files.signFile(actor.orgId, item.thumbnailAssetId), THUMBNAIL_OF[owner], id);
|
|
301
|
-
}
|
|
302
|
-
async spaceImageFile(spaceId, image, actor) {
|
|
303
|
-
const system = this.system(actor);
|
|
304
|
-
const space = await this.content.getSpace(spaceId, system);
|
|
305
|
-
const assetId = space ? SPACE_IMAGE_ASSET[image](space) : null;
|
|
306
|
-
const community = space ? await this.content.findCommunity(space.communityId, system) : null;
|
|
307
|
-
if (!space || !assetId || !community || community.status !== "published" && !actor.permissions.has("communities:read", space.communityId)) throw new NotFoundError("Space image", spaceId);
|
|
308
|
-
return signed(await this.files.signFile(actor.orgId, assetId), "Space image", spaceId);
|
|
309
|
-
}
|
|
310
|
-
async activityFile(activityId, assetId, actor) {
|
|
311
|
-
const system = this.system(actor);
|
|
312
|
-
const activity = await this.content.getActivity(activityId, system);
|
|
313
|
-
if (!activity) throw new NotFoundError("Lesson file", assetId);
|
|
314
|
-
if (!actor.permissions.has("courses:read", activity.courseId)) {
|
|
315
|
-
if (!isActivityPublished(activity.settings) || !await this.opensCourse(activity.courseId, actor)) throw new NotFoundError("Lesson file", assetId);
|
|
316
|
-
}
|
|
317
|
-
if (!(await this.content.listCourseActivityAssets(activity.courseId, system)).some((link) => link.activityId === activityId && link.assetId === assetId)) throw new NotFoundError("Lesson file", assetId);
|
|
318
|
-
return signed(await this.files.signFile(actor.orgId, assetId), "Lesson file", assetId);
|
|
319
|
-
}
|
|
320
|
-
async downloadFile(downloadId, assetId, actor) {
|
|
321
|
-
if (!await this.opensDownload(downloadId, actor)) throw new NotFoundError("Download file", assetId);
|
|
322
|
-
const file = (await this.content.listDownloadAssets(downloadId, this.system(actor))).find((f) => f.assetId === assetId);
|
|
323
|
-
if (!file) throw new NotFoundError("Download file", assetId);
|
|
324
|
-
return signed(await this.files.signFile(actor.orgId, assetId, { saveAs: file.displayName ?? file.filename }), "Download file", assetId);
|
|
325
|
-
}
|
|
326
283
|
async seeEvents(ids, actor) {
|
|
327
284
|
if (ids.length === 0) return [];
|
|
328
285
|
const system = this.system(actor);
|
|
@@ -443,13 +400,6 @@ var MemberReportServiceImpl = class {
|
|
|
443
400
|
rows: page.rows.map((row) => attendeeView(actor.orgId, row, this.fileUrls))
|
|
444
401
|
};
|
|
445
402
|
}
|
|
446
|
-
async eventFile(eventId, assetId, actor) {
|
|
447
|
-
const [seen] = await this.seeEvents([eventId], actor);
|
|
448
|
-
if (!seen || seen.sight === "public") throw new NotFoundError("Event file", assetId);
|
|
449
|
-
const file = (await this.content.eventFilesFor([eventId], this.system(actor)))[eventId]?.find((f) => f.assetId === assetId);
|
|
450
|
-
if (!file) throw new NotFoundError("Event file", assetId);
|
|
451
|
-
return signed(await this.files.signFile(actor.orgId, assetId, { saveAs: file.displayName ?? file.filename }), "Event file", assetId);
|
|
452
|
-
}
|
|
453
403
|
async directory(communityId, query, actor) {
|
|
454
404
|
if (!await this.access.isCommunityMember(communityId, actor)) throw new NotFoundError("Community", communityId);
|
|
455
405
|
const orgId = actor.orgId;
|
|
@@ -486,9 +436,9 @@ var MemberReportServiceImpl = class {
|
|
|
486
436
|
communityId,
|
|
487
437
|
title: community.title,
|
|
488
438
|
description: community.description,
|
|
489
|
-
thumbnailUrl: community.thumbnailUrl,
|
|
439
|
+
thumbnailUrl: showsImages(community.status, communityId, actor) ? community.thumbnailUrl : null,
|
|
490
440
|
dm: community.settings.dm,
|
|
491
|
-
spaces: await this.homeSpaces(actor, communityId, spaces, everySpace),
|
|
441
|
+
spaces: await this.homeSpaces(actor, communityId, spaces, everySpace, showsImages(community.status, communityId, actor)),
|
|
492
442
|
viewer: {
|
|
493
443
|
permissions: PERMISSION_KEYS.filter((p) => actor.permissions.has(p, communityId)),
|
|
494
444
|
opensEverySpace: everySpace,
|
|
@@ -500,14 +450,15 @@ var MemberReportServiceImpl = class {
|
|
|
500
450
|
}
|
|
501
451
|
async communitySpace(communityId, spaceId, actor) {
|
|
502
452
|
if (!await this.access.isCommunityMember(communityId, actor)) throw new NotFoundError("Space", spaceId);
|
|
503
|
-
const
|
|
504
|
-
|
|
453
|
+
const system = this.system(actor);
|
|
454
|
+
const [space, community] = await Promise.all([this.content.getSpace(spaceId, system), this.content.findCommunity(communityId, system)]);
|
|
455
|
+
if (!space || space.communityId !== communityId || !community) throw new NotFoundError("Space", spaceId);
|
|
505
456
|
const everySpace = await this.access.opensEverySpace(actor, communityId);
|
|
506
|
-
const [view] = await this.homeSpaces(actor, communityId, [space], everySpace);
|
|
457
|
+
const [view] = await this.homeSpaces(actor, communityId, [space], everySpace, showsImages(community.status, communityId, actor));
|
|
507
458
|
if (!view) throw new NotFoundError("Space", spaceId);
|
|
508
459
|
return view;
|
|
509
460
|
}
|
|
510
|
-
async homeSpaces(actor, communityId, spaces, everySpace) {
|
|
461
|
+
async homeSpaces(actor, communityId, spaces, everySpace, images) {
|
|
511
462
|
const orgId = actor.orgId;
|
|
512
463
|
const excluding = await this.access.everySpaceOpeners(orgId, communityId);
|
|
513
464
|
const [openIds, counts, standings, subscribed] = await Promise.all([
|
|
@@ -526,9 +477,9 @@ var MemberReportServiceImpl = class {
|
|
|
526
477
|
memberCount,
|
|
527
478
|
accessRule: space.accessRule,
|
|
528
479
|
standing: standingOf(space, open.has(space.id), memberCount, standings[space.id]),
|
|
529
|
-
coverUrl: space.coverUrl,
|
|
530
|
-
backgroundUrl: space.backgroundUrl,
|
|
531
|
-
iconUrl: space.iconUrl
|
|
480
|
+
coverUrl: images ? space.coverUrl : null,
|
|
481
|
+
backgroundUrl: images ? space.backgroundUrl : null,
|
|
482
|
+
iconUrl: images ? space.iconUrl : null
|
|
532
483
|
};
|
|
533
484
|
if (!open.has(space.id)) return {
|
|
534
485
|
...base,
|
|
@@ -578,9 +529,8 @@ var MemberReportServiceImpl = class {
|
|
|
578
529
|
}))).filter((view) => view !== null);
|
|
579
530
|
}
|
|
580
531
|
};
|
|
581
|
-
function
|
|
582
|
-
|
|
583
|
-
return url;
|
|
532
|
+
function showsImages(status, communityId, actor) {
|
|
533
|
+
return status === "published" || actor.permissions.has("communities:read", communityId);
|
|
584
534
|
}
|
|
585
535
|
function standingOf(space, open, memberCount, status) {
|
|
586
536
|
if (space.accessRule === "all_members" || open && status !== "active") return "open";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../../src/reporting/member/service.ts"],"sourcesContent":["import {\n eventSight,\n type ContentServicePort,\n type EventSight,\n type EventView,\n type SpaceDetail,\n type SpaceImage,\n type SpaceView,\n} from '../../content/index.js';\nimport { NotFoundError } from '../../shared/errors.js';\nimport { seesSpace, type EntitlementsService, type SpaceMember } from '../../entitlements/index.js';\nimport type { ProgressService } from '../../progress/index.js';\nimport type { AssetFiles, FileUrls } from '../../assets/index.js';\nimport type { OrganizationService } from '../../organizations/index.js';\nimport { PERMISSION_KEYS } from '../../types/schemas/organizations.js';\nimport type {\n ItemTitles as DiscussionItemTitles,\n SubscriptionService,\n} from '../../discussion/index.js';\nimport type { ItemTitles as ContentItemTitles } from '../../content/index.js';\nimport type { MemberActor, NotificationSettingsService } from '../../identity/index.js';\nimport {\n applicableCategories,\n type ListedNotification,\n type NotificationsService,\n} from '../../automations/index.js';\nimport type { ChannelRegistry, NotificationCategoryService } from '../../shared/ports.js';\nimport { resolveChannels } from '../../shared/notification-settings.js';\nimport { channelOrder } from '../../shared/notification-categories.js';\nimport type {\n NotificationCategory,\n NotificationFilter,\n StoredSetting,\n SubjectRef,\n} from '../../types/index.js';\nimport type { Actor } from '../../shared/actor.js';\nimport type { EventFile, Page, Permission } from '../../types/index.js';\nimport type {\n ActivityView,\n CommunityHome,\n CommunityHomeLink,\n CommunityHomeSpace,\n CourseProgressView,\n CourseView,\n DirectoryEntry,\n DirectoryQuery,\n DownloadFile,\n DownloadView,\n EventAttendee,\n MemberEvent,\n MemberEventSummary,\n MemberEventsQuery,\n MemberViewer,\n Module,\n NotificationItemView,\n NotificationView,\n NotificationsView,\n NotificationUnreadCounts,\n CommunityNotificationSettingsView,\n NotificationSettingsCategoryView,\n OrgNotificationSettingsView,\n SettingsChannelView,\n} from './model.js';\nimport type { MemberReportRepository, MemberReportService } from './ports.js';\nimport { eventCalendar } from './calendar.js';\nimport { attendeeFace, attendeeView, type EventFaces } from '../events/index.js';\n\nfunction refKey(ref: SubjectRef): string {\n return `${ref.type}:${ref.id}`;\n}\n\nfunction itemView(\n ref: SubjectRef | null,\n titles: Record<string, { title: string; excerpt?: string }>,\n): NotificationItemView | null {\n const found = ref ? titles[refKey(ref)] : undefined;\n if (!ref || !found) {\n return null;\n }\n return found.excerpt !== undefined\n ? { type: ref.type, id: ref.id, excerpt: found.excerpt }\n : { type: ref.type, id: ref.id, title: found.title };\n}\n\nfunction isActivityPublished(settings: unknown): boolean {\n return (settings as { published?: boolean } | null)?.published !== false;\n}\n\ntype MemberAccess = Pick<\n EntitlementsService,\n | 'isCommunityMember'\n | 'openSpaceIds'\n | 'spaceStandings'\n | 'hasAccess'\n | 'listAccessibleContent'\n | 'openCommunityIds'\n | 'opensEverySpace'\n | 'everySpaceOpeners'\n>;\n\ntype MemberContent = Pick<\n ContentServicePort,\n | 'findCourse'\n | 'listCourseModules'\n | 'listCourseActivityViews'\n | 'listCourseActivities'\n | 'listCourseActivityAssets'\n | 'getActivity'\n | 'findDownload'\n | 'listDownloadAssets'\n | 'findCommunity'\n | 'listSpaces'\n | 'getSpace'\n | 'findEvent'\n | 'findEvents'\n | 'eventFilesFor'\n>;\n\ntype MemberPeople = Pick<OrganizationService, 'systemActor' | 'permissionsOfMany' | 'holders'>;\n\ntype Owner = 'course' | 'download' | 'community' | 'event';\n\nconst READ_OF = {\n course: 'courses:read',\n download: 'downloads:read',\n community: 'communities:read',\n event: 'events:read',\n} as const satisfies Record<Owner, Permission>;\n\nconst THUMBNAIL_OF = {\n course: 'Course thumbnail',\n download: 'Download thumbnail',\n community: 'Community thumbnail',\n event: 'Event thumbnail',\n} as const satisfies Record<Owner, string>;\n\nconst SPACE_IMAGE_ASSET = {\n cover: (space) => space.coverAssetId,\n background: (space) => space.backgroundAssetId,\n icon: (space) => space.iconAssetId,\n} as const satisfies Record<SpaceImage, (space: SpaceView) => string | null>;\n\nconst EVENT_FACES = 10;\n\ninterface SeenEvent {\n event: EventView;\n sight: EventSight;\n attending: boolean;\n}\n\nexport type MemberReportServiceParams = {\n repo: MemberReportRepository;\n content: MemberContent;\n progress: Pick<ProgressService, 'get' | 'listByTargets'>;\n files: Pick<AssetFiles, 'signFile'>;\n fileUrls: FileUrls;\n access: MemberAccess;\n people: MemberPeople;\n notifications: Pick<NotificationsService, 'list' | 'get' | 'unreadCounts'>;\n titles: { discussion: DiscussionItemTitles; content: ContentItemTitles };\n subscriptions: Pick<SubscriptionService, 'viewerSubscriptions'>;\n notificationCategories: {\n community: Pick<NotificationCategoryService, 'get'>;\n org: Pick<NotificationCategoryService, 'get'>;\n };\n notificationSettings: Pick<NotificationSettingsService, 'get'>;\n channels: Pick<ChannelRegistry, 'list'>;\n studentPortalUrl: string;\n now?: () => Date;\n};\n\nexport class MemberReportServiceImpl implements MemberReportService {\n private readonly repo: MemberReportRepository;\n private readonly content: MemberContent;\n private readonly progress: Pick<ProgressService, 'get' | 'listByTargets'>;\n private readonly files: Pick<AssetFiles, 'signFile'>;\n private readonly fileUrls: FileUrls;\n private readonly access: MemberAccess;\n private readonly people: MemberPeople;\n private readonly notificationList: Pick<NotificationsService, 'list' | 'get' | 'unreadCounts'>;\n private readonly titles: MemberReportServiceParams['titles'];\n private readonly subscriptions: Pick<SubscriptionService, 'viewerSubscriptions'>;\n private readonly notificationCategories: MemberReportServiceParams['notificationCategories'];\n private readonly notificationSettings: Pick<NotificationSettingsService, 'get'>;\n private readonly channels: Pick<ChannelRegistry, 'list'>;\n private readonly studentPortalUrl: string;\n private readonly now: () => Date;\n\n constructor(params: MemberReportServiceParams) {\n this.repo = params.repo;\n this.content = params.content;\n this.progress = params.progress;\n this.files = params.files;\n this.fileUrls = params.fileUrls;\n this.access = params.access;\n this.people = params.people;\n this.notificationList = params.notifications;\n this.titles = params.titles;\n this.subscriptions = params.subscriptions;\n this.notificationCategories = params.notificationCategories;\n this.notificationSettings = params.notificationSettings;\n this.channels = params.channels;\n this.studentPortalUrl = params.studentPortalUrl.replace(/\\/+$/, '');\n this.now = params.now ?? (() => new Date());\n }\n\n private system(actor: Actor): Actor {\n return this.people.systemActor(actor.orgId);\n }\n\n viewer(actor: Actor): MemberViewer {\n const role = actor.permissions.roleOn();\n return {\n orgUserId: actor.id,\n orgRole: { key: role.key, name: role.name },\n permissions: PERMISSION_KEYS.filter((p) => actor.permissions.anywhere(p)),\n orgPermissions: PERMISSION_KEYS.filter((p) => actor.permissions.has(p)),\n };\n }\n\n async communityNotificationSettings(\n communityId: string,\n actor: MemberActor,\n ): Promise<CommunityNotificationSettingsView> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new NotFoundError('Community', communityId);\n }\n const orgId = actor.orgId;\n const system = this.system(actor);\n const [list, settings, spaceIds, spaces, community] = await Promise.all([\n this.notificationCategories.community.get(communityId, system),\n this.notificationSettings.get(orgId, actor.userId, { communityId }),\n this.access.openSpaceIds(communityId, actor),\n this.content.listSpaces(communityId, system),\n this.content.findCommunity(communityId, system),\n ]);\n const open = new Set(spaceIds);\n return {\n communityId,\n digest: community?.settings.digest ?? null,\n categories: this.settingsView(\n applicableCategories(list.categories, actor.permissions, { communityId }),\n settings,\n spaces\n .filter((space) => open.has(space.id))\n .map((space) => ({ spaceId: space.id, name: space.name })),\n ),\n };\n }\n\n async orgNotificationSettings(actor: MemberActor): Promise<OrgNotificationSettingsView> {\n const orgId = actor.orgId;\n const [list, settings] = await Promise.all([\n this.notificationCategories.org.get(orgId, this.system(actor)),\n this.notificationSettings.get(orgId, actor.userId, 'org'),\n ]);\n return {\n categories: this.settingsView(\n applicableCategories(list.categories, actor.permissions, 'org'),\n settings,\n [],\n ),\n };\n }\n\n private settingsView(\n categories: NotificationCategory[],\n settings: StoredSetting[],\n spaces: { spaceId: string; name: string }[],\n ): NotificationSettingsCategoryView[] {\n const configured = channelOrder(this.channels.list());\n const channelsAt = (\n category: NotificationCategory,\n spaceId: string | null,\n ): SettingsChannelView[] => {\n const withoutLevel = settings.filter(\n (setting) => setting.category !== category.key || setting.spaceId !== spaceId,\n );\n const fallback = new Map(\n resolveChannels(category, withoutLevel, spaceId, configured).map((resolved) => [\n resolved.channel,\n resolved.enabled,\n ]),\n );\n return resolveChannels(category, settings, spaceId, configured).map((resolved) => ({\n ...resolved,\n default: fallback.get(resolved.channel) ?? resolved.enabled,\n }));\n };\n return categories.map((category) => {\n const perSpace = category.perSpace === true;\n const view: NotificationSettingsCategoryView = {\n key: category.key,\n perSpace,\n channels: channelsAt(category, null),\n };\n if (perSpace) {\n view.spaces = spaces.map(({ spaceId, name }) => ({\n spaceId,\n name,\n channels: channelsAt(category, spaceId),\n }));\n }\n return view;\n });\n }\n\n async notifications(\n filter: NotificationFilter,\n page: { page: number; pageSize: number },\n actor: MemberActor,\n ): Promise<NotificationsView> {\n const listed = await this.notificationList.list(filter, page, actor);\n return {\n rows: await this.describeNotifications(actor, listed.items),\n total: listed.total,\n page: page.page,\n pageSize: page.pageSize,\n unreadCount: listed.unreadCount,\n };\n }\n\n async notification(notificationId: string, actor: MemberActor): Promise<NotificationView> {\n const found = await this.notificationList.get(notificationId, actor);\n const [view] = found ? await this.describeNotifications(actor, [found]) : [];\n if (!view) {\n throw new NotFoundError('Notification', notificationId);\n }\n return view;\n }\n\n async notificationUnreadCounts(actor: MemberActor): Promise<NotificationUnreadCounts> {\n const communityIds = await this.access.openCommunityIds(actor);\n return this.notificationList.unreadCounts(communityIds, actor);\n }\n\n private async describeNotifications(\n actor: Actor,\n rows: ListedNotification[],\n ): Promise<NotificationView[]> {\n const orgId = actor.orgId;\n const refs = rows.flatMap((row) => [\n ...(row.subject ? [row.subject] : []),\n ...(row.parent ? [row.parent] : []),\n ...(row.communityId ? [{ type: 'community' as const, id: row.communityId }] : []),\n ...(row.spaceId ? [{ type: 'space' as const, id: row.spaceId }] : []),\n ]);\n const actorIds = [...new Set(rows.flatMap((row) => row.actorId ?? []))];\n const [people, discussionTitles, contentTitles] = await Promise.all([\n this.repo.people(orgId, actorIds),\n this.titles.discussion.titlesFor(refs, actor),\n this.titles.content.titlesFor(refs, actor),\n ]);\n const titles = { ...contentTitles, ...discussionTitles };\n const personById = new Map(people.map((person) => [person.orgUserId, person]));\n return rows.map((row) => {\n const person = row.actorId ? personById.get(row.actorId) : undefined;\n const community = row.communityId ? titles[`community:${row.communityId}`] : undefined;\n const space = row.spaceId ? titles[`space:${row.spaceId}`] : undefined;\n return {\n id: row.id,\n type: row.type,\n scope: row.scope,\n reasons: row.reasons,\n matchedReason: row.matchedReason,\n category: row.category,\n display: row.display,\n community:\n row.communityId && community ? { id: row.communityId, title: community.title } : null,\n space: row.spaceId && space ? { id: row.spaceId, name: space.title } : null,\n actor: person\n ? {\n orgUserId: person.orgUserId,\n name: person.displayName,\n imageUrl: person.imageAssetId\n ? this.fileUrls.publicImage(orgId, person.orgUserId, person.imageAssetId)\n : null,\n }\n : null,\n subject: itemView(row.subject, titles),\n parent: itemView(row.parent, titles),\n createdAt: row.createdAt,\n readAt: row.readAt,\n };\n });\n }\n\n private async opensCourse(courseId: string, actor: Actor): Promise<CourseView | null> {\n const reads = actor.permissions.has('courses:read', courseId);\n if (!reads && !(await this.access.hasAccess(actor.orgId, actor.id, courseId))) {\n return null;\n }\n const course = await this.content.findCourse(courseId, this.system(actor));\n return course && (reads || course.status === 'published') ? course : null;\n }\n\n private async opensDownload(downloadId: string, actor: Actor): Promise<DownloadView | null> {\n const reads = actor.permissions.has('downloads:read', downloadId);\n if (!reads && !(await this.access.hasAccess(actor.orgId, actor.id, downloadId))) {\n return null;\n }\n const download = await this.content.findDownload(downloadId, this.system(actor));\n return download && (reads || download.status === 'published') ? download : null;\n }\n\n async listCourses(actor: Actor): Promise<CourseView[]> {\n const ids = await this.access.listAccessibleContent(actor.orgId, actor.id, 'course');\n const system = this.system(actor);\n const courses = await Promise.all(ids.map((id) => this.content.findCourse(id, system)));\n return courses.filter((c): c is CourseView => c !== null && c.status === 'published');\n }\n\n private async requireCourse(courseId: string, actor: Actor): Promise<CourseView> {\n const course = await this.opensCourse(courseId, actor);\n if (!course) {\n throw new NotFoundError('Course', courseId);\n }\n return course;\n }\n\n getCourse(courseId: string, actor: Actor): Promise<CourseView> {\n return this.requireCourse(courseId, actor);\n }\n\n async listModules(courseId: string, actor: Actor): Promise<Module[]> {\n await this.requireCourse(courseId, actor);\n return this.content.listCourseModules(courseId, this.system(actor));\n }\n\n async listActivities(courseId: string, actor: Actor): Promise<ActivityView[]> {\n await this.requireCourse(courseId, actor);\n const activities = await this.content.listCourseActivityViews(courseId, this.system(actor));\n return activities.filter((a) => isActivityPublished(a.settings));\n }\n\n async courseProgress(courseId: string, actor: Actor): Promise<CourseProgressView> {\n await this.requireCourse(courseId, actor);\n const orgId = actor.orgId;\n const courseActivities = await this.content.listCourseActivities(courseId, this.system(actor));\n const ids = courseActivities.filter((a) => isActivityPublished(a.settings)).map((a) => a.id);\n const records = await this.progress.listByTargets(orgId, actor.id, ids);\n const activities: CourseProgressView['activities'] = {};\n const positions: CourseProgressView['positions'] = {};\n let done = 0;\n for (const r of records) {\n if (r.targetType !== 'activity') {\n continue;\n }\n activities[r.targetId] = r.completedAt ? 'completed' : 'in-progress';\n if (r.completedAt) {\n done += 1;\n }\n if (r.position != null) {\n positions[r.targetId] = r.position;\n }\n }\n const courseRecord = await this.progress.get(orgId, {\n orgUserId: actor.id,\n targetType: 'course',\n targetId: courseId,\n });\n return {\n activities,\n percent: ids.length > 0 ? Math.round((done / ids.length) * 100) : 0,\n completed: courseRecord?.completedAt != null,\n positions,\n };\n }\n\n async listDownloads(actor: Actor): Promise<DownloadView[]> {\n const ids = await this.access.listAccessibleContent(actor.orgId, actor.id, 'download');\n const system = this.system(actor);\n const rows = await Promise.all(ids.map((id) => this.content.findDownload(id, system)));\n return rows.filter((d): d is DownloadView => d !== null && d.status === 'published');\n }\n\n async getDownload(\n downloadId: string,\n actor: Actor,\n ): Promise<{ download: DownloadView; assets: DownloadFile[] }> {\n const download = await this.opensDownload(downloadId, actor);\n if (!download) {\n throw new NotFoundError('Download', downloadId);\n }\n const assets = await this.content.listDownloadAssets(downloadId, this.system(actor));\n return { download, assets };\n }\n\n async thumbnailFile(owner: Owner, id: string, actor: Actor): Promise<string> {\n const system = this.system(actor);\n const item =\n owner === 'event'\n ? (await this.seeEvents([id], actor))[0]?.event\n : owner === 'course'\n ? await this.content.findCourse(id, system)\n : owner === 'download'\n ? await this.content.findDownload(id, system)\n : await this.content.findCommunity(id, system);\n if (\n !item?.thumbnailAssetId ||\n (owner !== 'event' &&\n item.status !== 'published' &&\n !actor.permissions.has(READ_OF[owner], id))\n ) {\n throw new NotFoundError(THUMBNAIL_OF[owner], id);\n }\n return signed(\n await this.files.signFile(actor.orgId, item.thumbnailAssetId),\n THUMBNAIL_OF[owner],\n id,\n );\n }\n\n async spaceImageFile(spaceId: string, image: SpaceImage, actor: Actor): Promise<string> {\n const system = this.system(actor);\n const space = await this.content.getSpace(spaceId, system);\n const assetId = space ? SPACE_IMAGE_ASSET[image](space) : null;\n const community = space ? await this.content.findCommunity(space.communityId, system) : null;\n if (\n !space ||\n !assetId ||\n !community ||\n (community.status !== 'published' &&\n !actor.permissions.has('communities:read', space.communityId))\n ) {\n throw new NotFoundError('Space image', spaceId);\n }\n return signed(await this.files.signFile(actor.orgId, assetId), 'Space image', spaceId);\n }\n\n async activityFile(activityId: string, assetId: string, actor: Actor): Promise<string> {\n const system = this.system(actor);\n const activity = await this.content.getActivity(activityId, system);\n if (!activity) {\n throw new NotFoundError('Lesson file', assetId);\n }\n if (!actor.permissions.has('courses:read', activity.courseId)) {\n if (\n !isActivityPublished(activity.settings) ||\n !(await this.opensCourse(activity.courseId, actor))\n ) {\n throw new NotFoundError('Lesson file', assetId);\n }\n }\n const links = await this.content.listCourseActivityAssets(activity.courseId, system);\n if (!links.some((link) => link.activityId === activityId && link.assetId === assetId)) {\n throw new NotFoundError('Lesson file', assetId);\n }\n return signed(await this.files.signFile(actor.orgId, assetId), 'Lesson file', assetId);\n }\n\n async downloadFile(downloadId: string, assetId: string, actor: Actor): Promise<string> {\n if (!(await this.opensDownload(downloadId, actor))) {\n throw new NotFoundError('Download file', assetId);\n }\n const files = await this.content.listDownloadAssets(downloadId, this.system(actor));\n const file = files.find((f) => f.assetId === assetId);\n if (!file) {\n throw new NotFoundError('Download file', assetId);\n }\n return signed(\n await this.files.signFile(actor.orgId, assetId, {\n saveAs: file.displayName ?? file.filename,\n }),\n 'Download file',\n assetId,\n );\n }\n\n private async seeEvents(ids: string[], actor: Actor): Promise<SeenEvent[]> {\n if (ids.length === 0) {\n return [];\n }\n const system = this.system(actor);\n const [events, attended] = await Promise.all([\n this.content.findEvents(ids, system),\n ids.length === 1\n ? this.access\n .hasAccess(actor.orgId, actor.id, ids[0]!)\n .then((attending) => (attending ? [ids[0]!] : []))\n : this.access.listAccessibleContent(actor.orgId, actor.id, 'event'),\n ]);\n const attending = new Set(attended);\n const byId = new Map(events.map((event) => [event.id, event]));\n return ids.flatMap((id) => {\n const event = byId.get(id);\n if (!event) {\n return [];\n }\n const sight = eventSight(event, {\n canRead: actor.permissions.has('events:read', id),\n attending: attending.has(id),\n });\n return sight ? [{ event, sight, attending: attending.has(id) }] : [];\n });\n }\n\n private async requireEvent(eventId: string, actor: Actor): Promise<SeenEvent> {\n const [seen] = await this.seeEvents([eventId], actor);\n if (!seen) {\n throw new NotFoundError('Event', eventId);\n }\n return seen;\n }\n\n private async eventSummaries(\n seen: SeenEvent[],\n actor: Actor,\n now: Date,\n ): Promise<MemberEventSummary[]> {\n const ids = seen.map((item) => item.event.id);\n const attended = seen.filter((item) => item.attending).map((item) => item.event.id);\n const [faces, direct] = await Promise.all([\n this.repo.eventFaces(actor.orgId, ids, EVENT_FACES),\n attended.length > 0\n ? this.repo.directEventGrants(actor.orgId, actor.id, attended)\n : Promise.resolve([]),\n ]);\n const leaves = new Set(direct);\n return seen.map((item) =>\n this.eventSummary(\n item,\n faces[item.event.id] ?? { total: 0, rows: [] },\n leaves.has(item.event.id),\n actor,\n now,\n ),\n );\n }\n\n private eventSummary(\n seen: SeenEvent,\n faces: EventFaces,\n canLeave: boolean,\n actor: Actor,\n now: Date,\n ): MemberEventSummary {\n const { event, sight, attending } = seen;\n const id = event.id;\n const canOpen = sight !== 'public';\n const capacity = event.settings.capacity;\n const attendeeCount = faces.total;\n const ended = event.endsAt.getTime() <= now.getTime();\n const recording =\n sight === 'read' || (sight === 'attendee' && ended && event.settings.replay)\n ? event.recording\n : null;\n const writes = actor.permissions.has('events:write', id);\n const canManageAttendees = actor.permissions.has('access:write') || writes;\n return {\n orgId: event.orgId,\n id,\n type: event.type,\n title: event.title,\n thumbnailAssetId: event.thumbnailAssetId,\n thumbnailUrl: event.thumbnailUrl,\n status: event.status,\n startsAt: event.startsAt,\n endsAt: event.endsAt,\n timeZone: event.timeZone,\n location: canOpen ? event.location : null,\n recording,\n settings: {\n type: event.settings.type,\n capacity,\n replay: event.settings.replay,\n },\n attendeeCount,\n attendees: faces.rows.map((row) => attendeeFace(actor.orgId, row, this.fileUrls)),\n placesLeft: capacity === null ? null : Math.max(capacity - attendeeCount, 0),\n viewer: {\n attending,\n canRegister:\n event.status === 'published' &&\n !attending &&\n event.settings.type === 'public' &&\n event.startsAt.getTime() > now.getTime() &&\n (capacity === null || attendeeCount < capacity),\n canOpen,\n canLeave,\n canManageAttendees,\n canAddAttendees: canManageAttendees && event.status === 'published' && !ended,\n canEdit: writes,\n canCancel: writes,\n canDelete: actor.permissions.has('events:delete', id),\n },\n };\n }\n\n async getEvent(eventId: string, actor: Actor): Promise<MemberEvent> {\n const seen = await this.requireEvent(eventId, actor);\n const now = this.now();\n const [[summary], files] = await Promise.all([\n this.eventSummaries([seen], actor, now),\n seen.sight === 'public'\n ? Promise.resolve({} as Record<string, EventFile[]>)\n : this.content.eventFilesFor([eventId], this.system(actor)),\n ]);\n return { ...summary!, description: seen.event.description, files: files[eventId] ?? [] };\n }\n\n async listEvents(query: MemberEventsQuery, actor: Actor): Promise<Page<MemberEventSummary>> {\n const now = this.now();\n const reads = actor.permissions.where('events:read');\n const page = await this.repo.events(\n actor.orgId,\n query,\n { orgUserId: actor.id, readAll: reads.org, readIds: reads.content },\n now,\n );\n const rows = await this.eventSummaries(await this.seeEvents(page.rows, actor), actor, now);\n return { ...page, rows };\n }\n\n async eventCalendar(eventId: string, actor: Actor): Promise<string> {\n const { event, sight } = await this.requireEvent(eventId, actor);\n return eventCalendar(event, {\n pageUrl: `${this.studentPortalUrl}/events/${encodeURIComponent(event.id)}`,\n withLocation: sight !== 'public',\n now: this.now(),\n });\n }\n\n async eventAttendees(\n eventId: string,\n query: { page: number; pageSize: number },\n actor: Actor,\n ): Promise<Page<EventAttendee>> {\n actor.permissions.require('events:read', eventId);\n if (!(await this.content.findEvent(eventId, this.system(actor)))) {\n throw new NotFoundError('Event', eventId);\n }\n const page = await this.repo.eventAttendees(actor.orgId, eventId, query);\n return {\n ...page,\n rows: page.rows.map((row) => attendeeView(actor.orgId, row, this.fileUrls)),\n };\n }\n\n async eventFile(eventId: string, assetId: string, actor: Actor): Promise<string> {\n const [seen] = await this.seeEvents([eventId], actor);\n if (!seen || seen.sight === 'public') {\n throw new NotFoundError('Event file', assetId);\n }\n const files = await this.content.eventFilesFor([eventId], this.system(actor));\n const file = files[eventId]?.find((f) => f.assetId === assetId);\n if (!file) {\n throw new NotFoundError('Event file', assetId);\n }\n return signed(\n await this.files.signFile(actor.orgId, assetId, {\n saveAs: file.displayName ?? file.filename,\n }),\n 'Event file',\n assetId,\n );\n }\n\n async directory(\n communityId: string,\n query: DirectoryQuery,\n actor: Actor,\n ): Promise<Page<DirectoryEntry>> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new NotFoundError('Community', communityId);\n }\n const orgId = actor.orgId;\n const community = await this.content.findCommunity(communityId, this.system(actor));\n if (!community) {\n throw new NotFoundError('Community', communityId);\n }\n const readers = await this.people.holders(orgId, 'communities:read', communityId);\n const page = await this.repo.directory(orgId, communityId, query, readers);\n const permissions = await this.people.permissionsOfMany(\n orgId,\n page.rows.map((row) => row.orgUserId),\n );\n return {\n ...page,\n rows: page.rows.map((row) => {\n const held = permissions.get(row.orgUserId);\n const item = held?.roleOn(communityId);\n const org = held?.roleOn();\n return {\n ...row,\n imageUrl: row.imageAssetId\n ? this.fileUrls.publicImage(orgId, row.orgUserId, row.imageAssetId)\n : null,\n team: (item?.team ?? false) || (org?.team ?? false),\n roleName: item?.team ? item.name : org?.team ? org.name : null,\n opensEverySpace:\n (held?.has('communities:read', communityId) ||\n held?.has('communities:moderate', communityId)) ??\n false,\n };\n }),\n };\n }\n\n async communityHome(communityId: string, actor: Actor): Promise<CommunityHome> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new NotFoundError('Community', communityId);\n }\n const system = this.system(actor);\n const community = await this.content.findCommunity(communityId, system);\n if (!community) {\n throw new NotFoundError('Community', communityId);\n }\n const [spaces, everySpace] = await Promise.all([\n this.content.listSpaces(communityId, system),\n this.access.opensEverySpace(actor, communityId),\n ]);\n const itemRole = actor.permissions.roleOn(communityId);\n const orgRole = actor.permissions.roleOn();\n const role = itemRole.team || !orgRole.team ? itemRole : orgRole;\n return {\n communityId,\n title: community.title,\n description: community.description,\n thumbnailUrl: community.thumbnailUrl,\n dm: community.settings.dm,\n spaces: await this.homeSpaces(actor, communityId, spaces, everySpace),\n viewer: {\n permissions: PERMISSION_KEYS.filter((p) => actor.permissions.has(p, communityId)),\n opensEverySpace: everySpace,\n team: itemRole.team || orgRole.team,\n roleName: role.name,\n canCreateEvents: actor.permissions.has('events:write'),\n },\n };\n }\n\n async communitySpace(\n communityId: string,\n spaceId: string,\n actor: Actor,\n ): Promise<CommunityHomeSpace> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new NotFoundError('Space', spaceId);\n }\n const space = await this.content.getSpace(spaceId, this.system(actor));\n if (!space || space.communityId !== communityId) {\n throw new NotFoundError('Space', spaceId);\n }\n const everySpace = await this.access.opensEverySpace(actor, communityId);\n const [view] = await this.homeSpaces(actor, communityId, [space], everySpace);\n if (!view) {\n throw new NotFoundError('Space', spaceId);\n }\n return view;\n }\n\n private async homeSpaces(\n actor: Actor,\n communityId: string,\n spaces: SpaceView[],\n everySpace: boolean,\n ): Promise<CommunityHomeSpace[]> {\n const orgId = actor.orgId;\n const excluding = await this.access.everySpaceOpeners(orgId, communityId);\n const [openIds, counts, standings, subscribed] = await Promise.all([\n this.access.openSpaceIds(communityId, actor),\n this.repo.spaceMemberCounts(orgId, communityId, excluding),\n this.access.spaceStandings(communityId, actor),\n this.subscriptions.viewerSubscriptions(\n orgId,\n actor.id,\n 'space',\n spaces.map((space) => space.id),\n new Date(),\n ),\n ]);\n const open = new Set(openIds);\n const seen = spaces.filter((space) =>\n seesSpace(everySpace, space.accessRule, standings[space.id]),\n );\n return Promise.all(\n seen.map(async (space): Promise<CommunityHomeSpace> => {\n const memberCount = counts[space.id] ?? 0;\n const base = {\n id: space.id,\n name: space.name,\n memberCount,\n accessRule: space.accessRule,\n standing: standingOf(space, open.has(space.id), memberCount, standings[space.id]),\n coverUrl: space.coverUrl,\n backgroundUrl: space.backgroundUrl,\n iconUrl: space.iconUrl,\n };\n if (!open.has(space.id)) {\n return { ...base, locked: true };\n }\n return {\n ...base,\n locked: false,\n description: space.description,\n capacity: space.capacity,\n settings: space.settings,\n topics: space.topics.map((topic) => ({ id: topic.id, name: topic.name })),\n links: await this.linkViews(actor, space.links),\n subscription: subscribed[space.id] ?? null,\n };\n }),\n );\n }\n\n private async linkViews(actor: Actor, links: SpaceDetail['links']): Promise<CommunityHomeLink[]> {\n const system = this.system(actor);\n const shown = (type: keyof typeof READ_OF, id: string, status: string) =>\n status === 'published' || actor.permissions.has(READ_OF[type], id);\n const views = await Promise.all(\n links.map(async (link): Promise<CommunityHomeLink | null> => {\n const course = await this.content.findCourse(link.contentId, system);\n if (course) {\n return shown('course', link.contentId, course.status)\n ? { id: link.id, contentId: link.contentId, type: 'course', title: course.title }\n : null;\n }\n const download = await this.content.findDownload(link.contentId, system);\n if (download) {\n return shown('download', link.contentId, download.status)\n ? { id: link.id, contentId: link.contentId, type: 'download', title: download.title }\n : null;\n }\n const community = await this.content.findCommunity(link.contentId, system);\n if (community) {\n return shown('community', link.contentId, community.status)\n ? { id: link.id, contentId: link.contentId, type: 'community', title: community.title }\n : null;\n }\n return null;\n }),\n );\n return views.filter((view): view is CommunityHomeLink => view !== null);\n }\n}\n\nfunction signed(url: string | null, what: string, id: string): string {\n if (!url) {\n throw new NotFoundError(what, id);\n }\n return url;\n}\n\nfunction standingOf(\n space: SpaceDetail,\n open: boolean,\n memberCount: number,\n status: SpaceMember['status'] | undefined,\n): CommunityHomeSpace['standing'] {\n if (space.accessRule === 'all_members' || (open && status !== 'active')) {\n return 'open';\n }\n if (status === 'active') {\n return 'member';\n }\n if (status === 'requested' || status === 'invited') {\n return status;\n }\n if (space.capacity !== null && memberCount >= space.capacity) {\n return 'full';\n }\n return 'none';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../../src/reporting/member/service.ts"],"sourcesContent":["import {\n eventSight,\n type ContentServicePort,\n type EventSight,\n type EventView,\n type SpaceDetail,\n type SpaceView,\n} from '../../content/index.js';\nimport { NotFoundError } from '../../shared/errors.js';\nimport { seesSpace, type EntitlementsService, type SpaceMember } from '../../entitlements/index.js';\nimport type { ProgressService } from '../../progress/index.js';\nimport type { FileUrls } from '../../assets/index.js';\nimport type { OrganizationService } from '../../organizations/index.js';\nimport { PERMISSION_KEYS } from '../../types/schemas/organizations.js';\nimport type {\n ItemTitles as DiscussionItemTitles,\n SubscriptionService,\n} from '../../discussion/index.js';\nimport type { ItemTitles as ContentItemTitles } from '../../content/index.js';\nimport type { MemberActor, NotificationSettingsService } from '../../identity/index.js';\nimport {\n applicableCategories,\n type ListedNotification,\n type NotificationsService,\n} from '../../automations/index.js';\nimport type { ChannelRegistry, NotificationCategoryService } from '../../shared/ports.js';\nimport { resolveChannels } from '../../shared/notification-settings.js';\nimport { channelOrder } from '../../shared/notification-categories.js';\nimport type {\n NotificationCategory,\n NotificationFilter,\n StoredSetting,\n SubjectRef,\n} from '../../types/index.js';\nimport type { Actor } from '../../shared/actor.js';\nimport type { EventFile, Page, Permission } from '../../types/index.js';\nimport type {\n ActivityView,\n CommunityHome,\n CommunityHomeLink,\n CommunityHomeSpace,\n CourseProgressView,\n CourseView,\n DirectoryEntry,\n DirectoryQuery,\n DownloadFile,\n DownloadView,\n EventAttendee,\n MemberEvent,\n MemberEventSummary,\n MemberEventsQuery,\n MemberViewer,\n Module,\n NotificationItemView,\n NotificationView,\n NotificationsView,\n NotificationUnreadCounts,\n CommunityNotificationSettingsView,\n NotificationSettingsCategoryView,\n OrgNotificationSettingsView,\n SettingsChannelView,\n} from './model.js';\nimport type { MemberReportRepository, MemberReportService } from './ports.js';\nimport { eventCalendar } from './calendar.js';\nimport { attendeeFace, attendeeView, type EventFaces } from '../events/index.js';\n\nfunction refKey(ref: SubjectRef): string {\n return `${ref.type}:${ref.id}`;\n}\n\nfunction itemView(\n ref: SubjectRef | null,\n titles: Record<string, { title: string; excerpt?: string }>,\n): NotificationItemView | null {\n const found = ref ? titles[refKey(ref)] : undefined;\n if (!ref || !found) {\n return null;\n }\n return found.excerpt !== undefined\n ? { type: ref.type, id: ref.id, excerpt: found.excerpt }\n : { type: ref.type, id: ref.id, title: found.title };\n}\n\nfunction isActivityPublished(settings: unknown): boolean {\n return (settings as { published?: boolean } | null)?.published !== false;\n}\n\ntype MemberAccess = Pick<\n EntitlementsService,\n | 'isCommunityMember'\n | 'openSpaceIds'\n | 'spaceStandings'\n | 'hasAccess'\n | 'listAccessibleContent'\n | 'openCommunityIds'\n | 'opensEverySpace'\n | 'everySpaceOpeners'\n>;\n\ntype MemberContent = Pick<\n ContentServicePort,\n | 'findCourse'\n | 'listCourseModules'\n | 'listCourseActivityViews'\n | 'listCourseActivities'\n | 'findDownload'\n | 'listDownloadAssets'\n | 'findCommunity'\n | 'listSpaces'\n | 'getSpace'\n | 'findEvent'\n | 'findEvents'\n | 'eventFilesFor'\n>;\n\ntype MemberPeople = Pick<OrganizationService, 'systemActor' | 'permissionsOfMany' | 'holders'>;\n\ntype Owner = 'course' | 'download' | 'community' | 'event';\n\nconst READ_OF = {\n course: 'courses:read',\n download: 'downloads:read',\n community: 'communities:read',\n event: 'events:read',\n} as const satisfies Record<Owner, Permission>;\n\nconst EVENT_FACES = 10;\n\ninterface SeenEvent {\n event: EventView;\n sight: EventSight;\n attending: boolean;\n}\n\nexport type MemberReportServiceParams = {\n repo: MemberReportRepository;\n content: MemberContent;\n progress: Pick<ProgressService, 'get' | 'listByTargets'>;\n fileUrls: FileUrls;\n access: MemberAccess;\n people: MemberPeople;\n notifications: Pick<NotificationsService, 'list' | 'get' | 'unreadCounts'>;\n titles: { discussion: DiscussionItemTitles; content: ContentItemTitles };\n subscriptions: Pick<SubscriptionService, 'viewerSubscriptions'>;\n notificationCategories: {\n community: Pick<NotificationCategoryService, 'get'>;\n org: Pick<NotificationCategoryService, 'get'>;\n };\n notificationSettings: Pick<NotificationSettingsService, 'get'>;\n channels: Pick<ChannelRegistry, 'list'>;\n studentPortalUrl: string;\n now?: () => Date;\n};\n\nexport class MemberReportServiceImpl implements MemberReportService {\n private readonly repo: MemberReportRepository;\n private readonly content: MemberContent;\n private readonly progress: Pick<ProgressService, 'get' | 'listByTargets'>;\n private readonly fileUrls: FileUrls;\n private readonly access: MemberAccess;\n private readonly people: MemberPeople;\n private readonly notificationList: Pick<NotificationsService, 'list' | 'get' | 'unreadCounts'>;\n private readonly titles: MemberReportServiceParams['titles'];\n private readonly subscriptions: Pick<SubscriptionService, 'viewerSubscriptions'>;\n private readonly notificationCategories: MemberReportServiceParams['notificationCategories'];\n private readonly notificationSettings: Pick<NotificationSettingsService, 'get'>;\n private readonly channels: Pick<ChannelRegistry, 'list'>;\n private readonly studentPortalUrl: string;\n private readonly now: () => Date;\n\n constructor(params: MemberReportServiceParams) {\n this.repo = params.repo;\n this.content = params.content;\n this.progress = params.progress;\n this.fileUrls = params.fileUrls;\n this.access = params.access;\n this.people = params.people;\n this.notificationList = params.notifications;\n this.titles = params.titles;\n this.subscriptions = params.subscriptions;\n this.notificationCategories = params.notificationCategories;\n this.notificationSettings = params.notificationSettings;\n this.channels = params.channels;\n this.studentPortalUrl = params.studentPortalUrl.replace(/\\/+$/, '');\n this.now = params.now ?? (() => new Date());\n }\n\n private system(actor: Actor): Actor {\n return this.people.systemActor(actor.orgId);\n }\n\n viewer(actor: Actor): MemberViewer {\n const role = actor.permissions.roleOn();\n return {\n orgUserId: actor.id,\n orgRole: { key: role.key, name: role.name },\n permissions: PERMISSION_KEYS.filter((p) => actor.permissions.anywhere(p)),\n orgPermissions: PERMISSION_KEYS.filter((p) => actor.permissions.has(p)),\n };\n }\n\n async communityNotificationSettings(\n communityId: string,\n actor: MemberActor,\n ): Promise<CommunityNotificationSettingsView> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new NotFoundError('Community', communityId);\n }\n const orgId = actor.orgId;\n const system = this.system(actor);\n const [list, settings, spaceIds, spaces, community] = await Promise.all([\n this.notificationCategories.community.get(communityId, system),\n this.notificationSettings.get(orgId, actor.userId, { communityId }),\n this.access.openSpaceIds(communityId, actor),\n this.content.listSpaces(communityId, system),\n this.content.findCommunity(communityId, system),\n ]);\n const open = new Set(spaceIds);\n return {\n communityId,\n digest: community?.settings.digest ?? null,\n categories: this.settingsView(\n applicableCategories(list.categories, actor.permissions, { communityId }),\n settings,\n spaces\n .filter((space) => open.has(space.id))\n .map((space) => ({ spaceId: space.id, name: space.name })),\n ),\n };\n }\n\n async orgNotificationSettings(actor: MemberActor): Promise<OrgNotificationSettingsView> {\n const orgId = actor.orgId;\n const [list, settings] = await Promise.all([\n this.notificationCategories.org.get(orgId, this.system(actor)),\n this.notificationSettings.get(orgId, actor.userId, 'org'),\n ]);\n return {\n categories: this.settingsView(\n applicableCategories(list.categories, actor.permissions, 'org'),\n settings,\n [],\n ),\n };\n }\n\n private settingsView(\n categories: NotificationCategory[],\n settings: StoredSetting[],\n spaces: { spaceId: string; name: string }[],\n ): NotificationSettingsCategoryView[] {\n const configured = channelOrder(this.channels.list());\n const channelsAt = (\n category: NotificationCategory,\n spaceId: string | null,\n ): SettingsChannelView[] => {\n const withoutLevel = settings.filter(\n (setting) => setting.category !== category.key || setting.spaceId !== spaceId,\n );\n const fallback = new Map(\n resolveChannels(category, withoutLevel, spaceId, configured).map((resolved) => [\n resolved.channel,\n resolved.enabled,\n ]),\n );\n return resolveChannels(category, settings, spaceId, configured).map((resolved) => ({\n ...resolved,\n default: fallback.get(resolved.channel) ?? resolved.enabled,\n }));\n };\n return categories.map((category) => {\n const perSpace = category.perSpace === true;\n const view: NotificationSettingsCategoryView = {\n key: category.key,\n perSpace,\n channels: channelsAt(category, null),\n };\n if (perSpace) {\n view.spaces = spaces.map(({ spaceId, name }) => ({\n spaceId,\n name,\n channels: channelsAt(category, spaceId),\n }));\n }\n return view;\n });\n }\n\n async notifications(\n filter: NotificationFilter,\n page: { page: number; pageSize: number },\n actor: MemberActor,\n ): Promise<NotificationsView> {\n const listed = await this.notificationList.list(filter, page, actor);\n return {\n rows: await this.describeNotifications(actor, listed.items),\n total: listed.total,\n page: page.page,\n pageSize: page.pageSize,\n unreadCount: listed.unreadCount,\n };\n }\n\n async notification(notificationId: string, actor: MemberActor): Promise<NotificationView> {\n const found = await this.notificationList.get(notificationId, actor);\n const [view] = found ? await this.describeNotifications(actor, [found]) : [];\n if (!view) {\n throw new NotFoundError('Notification', notificationId);\n }\n return view;\n }\n\n async notificationUnreadCounts(actor: MemberActor): Promise<NotificationUnreadCounts> {\n const communityIds = await this.access.openCommunityIds(actor);\n return this.notificationList.unreadCounts(communityIds, actor);\n }\n\n private async describeNotifications(\n actor: Actor,\n rows: ListedNotification[],\n ): Promise<NotificationView[]> {\n const orgId = actor.orgId;\n const refs = rows.flatMap((row) => [\n ...(row.subject ? [row.subject] : []),\n ...(row.parent ? [row.parent] : []),\n ...(row.communityId ? [{ type: 'community' as const, id: row.communityId }] : []),\n ...(row.spaceId ? [{ type: 'space' as const, id: row.spaceId }] : []),\n ]);\n const actorIds = [...new Set(rows.flatMap((row) => row.actorId ?? []))];\n const [people, discussionTitles, contentTitles] = await Promise.all([\n this.repo.people(orgId, actorIds),\n this.titles.discussion.titlesFor(refs, actor),\n this.titles.content.titlesFor(refs, actor),\n ]);\n const titles = { ...contentTitles, ...discussionTitles };\n const personById = new Map(people.map((person) => [person.orgUserId, person]));\n return rows.map((row) => {\n const person = row.actorId ? personById.get(row.actorId) : undefined;\n const community = row.communityId ? titles[`community:${row.communityId}`] : undefined;\n const space = row.spaceId ? titles[`space:${row.spaceId}`] : undefined;\n return {\n id: row.id,\n type: row.type,\n scope: row.scope,\n reasons: row.reasons,\n matchedReason: row.matchedReason,\n category: row.category,\n display: row.display,\n community:\n row.communityId && community ? { id: row.communityId, title: community.title } : null,\n space: row.spaceId && space ? { id: row.spaceId, name: space.title } : null,\n actor: person\n ? {\n orgUserId: person.orgUserId,\n name: person.displayName,\n imageUrl: person.imageAssetId\n ? this.fileUrls.publicImage(orgId, person.orgUserId, person.imageAssetId)\n : null,\n }\n : null,\n subject: itemView(row.subject, titles),\n parent: itemView(row.parent, titles),\n createdAt: row.createdAt,\n readAt: row.readAt,\n };\n });\n }\n\n private async opensCourse(courseId: string, actor: Actor): Promise<CourseView | null> {\n const reads = actor.permissions.has('courses:read', courseId);\n if (!reads && !(await this.access.hasAccess(actor.orgId, actor.id, courseId))) {\n return null;\n }\n const course = await this.content.findCourse(courseId, this.system(actor));\n return course && (reads || course.status === 'published') ? course : null;\n }\n\n private async opensDownload(downloadId: string, actor: Actor): Promise<DownloadView | null> {\n const reads = actor.permissions.has('downloads:read', downloadId);\n if (!reads && !(await this.access.hasAccess(actor.orgId, actor.id, downloadId))) {\n return null;\n }\n const download = await this.content.findDownload(downloadId, this.system(actor));\n return download && (reads || download.status === 'published') ? download : null;\n }\n\n async listCourses(actor: Actor): Promise<CourseView[]> {\n const ids = await this.access.listAccessibleContent(actor.orgId, actor.id, 'course');\n const system = this.system(actor);\n const courses = await Promise.all(ids.map((id) => this.content.findCourse(id, system)));\n return courses.filter((c): c is CourseView => c !== null && c.status === 'published');\n }\n\n private async requireCourse(courseId: string, actor: Actor): Promise<CourseView> {\n const course = await this.opensCourse(courseId, actor);\n if (!course) {\n throw new NotFoundError('Course', courseId);\n }\n return course;\n }\n\n getCourse(courseId: string, actor: Actor): Promise<CourseView> {\n return this.requireCourse(courseId, actor);\n }\n\n async listModules(courseId: string, actor: Actor): Promise<Module[]> {\n await this.requireCourse(courseId, actor);\n return this.content.listCourseModules(courseId, this.system(actor));\n }\n\n async listActivities(courseId: string, actor: Actor): Promise<ActivityView[]> {\n await this.requireCourse(courseId, actor);\n const activities = await this.content.listCourseActivityViews(courseId, this.system(actor));\n return activities.filter((a) => isActivityPublished(a.settings));\n }\n\n async courseProgress(courseId: string, actor: Actor): Promise<CourseProgressView> {\n await this.requireCourse(courseId, actor);\n const orgId = actor.orgId;\n const courseActivities = await this.content.listCourseActivities(courseId, this.system(actor));\n const ids = courseActivities.filter((a) => isActivityPublished(a.settings)).map((a) => a.id);\n const records = await this.progress.listByTargets(orgId, actor.id, ids);\n const activities: CourseProgressView['activities'] = {};\n const positions: CourseProgressView['positions'] = {};\n let done = 0;\n for (const r of records) {\n if (r.targetType !== 'activity') {\n continue;\n }\n activities[r.targetId] = r.completedAt ? 'completed' : 'in-progress';\n if (r.completedAt) {\n done += 1;\n }\n if (r.position != null) {\n positions[r.targetId] = r.position;\n }\n }\n const courseRecord = await this.progress.get(orgId, {\n orgUserId: actor.id,\n targetType: 'course',\n targetId: courseId,\n });\n return {\n activities,\n percent: ids.length > 0 ? Math.round((done / ids.length) * 100) : 0,\n completed: courseRecord?.completedAt != null,\n positions,\n };\n }\n\n async listDownloads(actor: Actor): Promise<DownloadView[]> {\n const ids = await this.access.listAccessibleContent(actor.orgId, actor.id, 'download');\n const system = this.system(actor);\n const rows = await Promise.all(ids.map((id) => this.content.findDownload(id, system)));\n return rows.filter((d): d is DownloadView => d !== null && d.status === 'published');\n }\n\n async getDownload(\n downloadId: string,\n actor: Actor,\n ): Promise<{ download: DownloadView; assets: DownloadFile[] }> {\n const download = await this.opensDownload(downloadId, actor);\n if (!download) {\n throw new NotFoundError('Download', downloadId);\n }\n const assets = await this.content.listDownloadAssets(downloadId, this.system(actor));\n return { download, assets };\n }\n\n private async seeEvents(ids: string[], actor: Actor): Promise<SeenEvent[]> {\n if (ids.length === 0) {\n return [];\n }\n const system = this.system(actor);\n const [events, attended] = await Promise.all([\n this.content.findEvents(ids, system),\n ids.length === 1\n ? this.access\n .hasAccess(actor.orgId, actor.id, ids[0]!)\n .then((attending) => (attending ? [ids[0]!] : []))\n : this.access.listAccessibleContent(actor.orgId, actor.id, 'event'),\n ]);\n const attending = new Set(attended);\n const byId = new Map(events.map((event) => [event.id, event]));\n return ids.flatMap((id) => {\n const event = byId.get(id);\n if (!event) {\n return [];\n }\n const sight = eventSight(event, {\n canRead: actor.permissions.has('events:read', id),\n attending: attending.has(id),\n });\n return sight ? [{ event, sight, attending: attending.has(id) }] : [];\n });\n }\n\n private async requireEvent(eventId: string, actor: Actor): Promise<SeenEvent> {\n const [seen] = await this.seeEvents([eventId], actor);\n if (!seen) {\n throw new NotFoundError('Event', eventId);\n }\n return seen;\n }\n\n private async eventSummaries(\n seen: SeenEvent[],\n actor: Actor,\n now: Date,\n ): Promise<MemberEventSummary[]> {\n const ids = seen.map((item) => item.event.id);\n const attended = seen.filter((item) => item.attending).map((item) => item.event.id);\n const [faces, direct] = await Promise.all([\n this.repo.eventFaces(actor.orgId, ids, EVENT_FACES),\n attended.length > 0\n ? this.repo.directEventGrants(actor.orgId, actor.id, attended)\n : Promise.resolve([]),\n ]);\n const leaves = new Set(direct);\n return seen.map((item) =>\n this.eventSummary(\n item,\n faces[item.event.id] ?? { total: 0, rows: [] },\n leaves.has(item.event.id),\n actor,\n now,\n ),\n );\n }\n\n private eventSummary(\n seen: SeenEvent,\n faces: EventFaces,\n canLeave: boolean,\n actor: Actor,\n now: Date,\n ): MemberEventSummary {\n const { event, sight, attending } = seen;\n const id = event.id;\n const canOpen = sight !== 'public';\n const capacity = event.settings.capacity;\n const attendeeCount = faces.total;\n const ended = event.endsAt.getTime() <= now.getTime();\n const recording =\n sight === 'read' || (sight === 'attendee' && ended && event.settings.replay)\n ? event.recording\n : null;\n const writes = actor.permissions.has('events:write', id);\n const canManageAttendees = actor.permissions.has('access:write') || writes;\n return {\n orgId: event.orgId,\n id,\n type: event.type,\n title: event.title,\n thumbnailAssetId: event.thumbnailAssetId,\n thumbnailUrl: event.thumbnailUrl,\n status: event.status,\n startsAt: event.startsAt,\n endsAt: event.endsAt,\n timeZone: event.timeZone,\n location: canOpen ? event.location : null,\n recording,\n settings: {\n type: event.settings.type,\n capacity,\n replay: event.settings.replay,\n },\n attendeeCount,\n attendees: faces.rows.map((row) => attendeeFace(actor.orgId, row, this.fileUrls)),\n placesLeft: capacity === null ? null : Math.max(capacity - attendeeCount, 0),\n viewer: {\n attending,\n canRegister:\n event.status === 'published' &&\n !attending &&\n event.settings.type === 'public' &&\n event.startsAt.getTime() > now.getTime() &&\n (capacity === null || attendeeCount < capacity),\n canOpen,\n canLeave,\n canManageAttendees,\n canAddAttendees: canManageAttendees && event.status === 'published' && !ended,\n canEdit: writes,\n canCancel: writes,\n canDelete: actor.permissions.has('events:delete', id),\n },\n };\n }\n\n async getEvent(eventId: string, actor: Actor): Promise<MemberEvent> {\n const seen = await this.requireEvent(eventId, actor);\n const now = this.now();\n const [[summary], files] = await Promise.all([\n this.eventSummaries([seen], actor, now),\n seen.sight === 'public'\n ? Promise.resolve({} as Record<string, EventFile[]>)\n : this.content.eventFilesFor([eventId], this.system(actor)),\n ]);\n return { ...summary!, description: seen.event.description, files: files[eventId] ?? [] };\n }\n\n async listEvents(query: MemberEventsQuery, actor: Actor): Promise<Page<MemberEventSummary>> {\n const now = this.now();\n const reads = actor.permissions.where('events:read');\n const page = await this.repo.events(\n actor.orgId,\n query,\n { orgUserId: actor.id, readAll: reads.org, readIds: reads.content },\n now,\n );\n const rows = await this.eventSummaries(await this.seeEvents(page.rows, actor), actor, now);\n return { ...page, rows };\n }\n\n async eventCalendar(eventId: string, actor: Actor): Promise<string> {\n const { event, sight } = await this.requireEvent(eventId, actor);\n return eventCalendar(event, {\n pageUrl: `${this.studentPortalUrl}/events/${encodeURIComponent(event.id)}`,\n withLocation: sight !== 'public',\n now: this.now(),\n });\n }\n\n async eventAttendees(\n eventId: string,\n query: { page: number; pageSize: number },\n actor: Actor,\n ): Promise<Page<EventAttendee>> {\n actor.permissions.require('events:read', eventId);\n if (!(await this.content.findEvent(eventId, this.system(actor)))) {\n throw new NotFoundError('Event', eventId);\n }\n const page = await this.repo.eventAttendees(actor.orgId, eventId, query);\n return {\n ...page,\n rows: page.rows.map((row) => attendeeView(actor.orgId, row, this.fileUrls)),\n };\n }\n\n async directory(\n communityId: string,\n query: DirectoryQuery,\n actor: Actor,\n ): Promise<Page<DirectoryEntry>> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new NotFoundError('Community', communityId);\n }\n const orgId = actor.orgId;\n const community = await this.content.findCommunity(communityId, this.system(actor));\n if (!community) {\n throw new NotFoundError('Community', communityId);\n }\n const readers = await this.people.holders(orgId, 'communities:read', communityId);\n const page = await this.repo.directory(orgId, communityId, query, readers);\n const permissions = await this.people.permissionsOfMany(\n orgId,\n page.rows.map((row) => row.orgUserId),\n );\n return {\n ...page,\n rows: page.rows.map((row) => {\n const held = permissions.get(row.orgUserId);\n const item = held?.roleOn(communityId);\n const org = held?.roleOn();\n return {\n ...row,\n imageUrl: row.imageAssetId\n ? this.fileUrls.publicImage(orgId, row.orgUserId, row.imageAssetId)\n : null,\n team: (item?.team ?? false) || (org?.team ?? false),\n roleName: item?.team ? item.name : org?.team ? org.name : null,\n opensEverySpace:\n (held?.has('communities:read', communityId) ||\n held?.has('communities:moderate', communityId)) ??\n false,\n };\n }),\n };\n }\n\n async communityHome(communityId: string, actor: Actor): Promise<CommunityHome> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new NotFoundError('Community', communityId);\n }\n const system = this.system(actor);\n const community = await this.content.findCommunity(communityId, system);\n if (!community) {\n throw new NotFoundError('Community', communityId);\n }\n const [spaces, everySpace] = await Promise.all([\n this.content.listSpaces(communityId, system),\n this.access.opensEverySpace(actor, communityId),\n ]);\n const itemRole = actor.permissions.roleOn(communityId);\n const orgRole = actor.permissions.roleOn();\n const role = itemRole.team || !orgRole.team ? itemRole : orgRole;\n return {\n communityId,\n title: community.title,\n description: community.description,\n thumbnailUrl: showsImages(community.status, communityId, actor)\n ? community.thumbnailUrl\n : null,\n dm: community.settings.dm,\n spaces: await this.homeSpaces(\n actor,\n communityId,\n spaces,\n everySpace,\n showsImages(community.status, communityId, actor),\n ),\n viewer: {\n permissions: PERMISSION_KEYS.filter((p) => actor.permissions.has(p, communityId)),\n opensEverySpace: everySpace,\n team: itemRole.team || orgRole.team,\n roleName: role.name,\n canCreateEvents: actor.permissions.has('events:write'),\n },\n };\n }\n\n async communitySpace(\n communityId: string,\n spaceId: string,\n actor: Actor,\n ): Promise<CommunityHomeSpace> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new NotFoundError('Space', spaceId);\n }\n const system = this.system(actor);\n const [space, community] = await Promise.all([\n this.content.getSpace(spaceId, system),\n this.content.findCommunity(communityId, system),\n ]);\n if (!space || space.communityId !== communityId || !community) {\n throw new NotFoundError('Space', spaceId);\n }\n const everySpace = await this.access.opensEverySpace(actor, communityId);\n const [view] = await this.homeSpaces(\n actor,\n communityId,\n [space],\n everySpace,\n showsImages(community.status, communityId, actor),\n );\n if (!view) {\n throw new NotFoundError('Space', spaceId);\n }\n return view;\n }\n\n private async homeSpaces(\n actor: Actor,\n communityId: string,\n spaces: SpaceView[],\n everySpace: boolean,\n images: boolean,\n ): Promise<CommunityHomeSpace[]> {\n const orgId = actor.orgId;\n const excluding = await this.access.everySpaceOpeners(orgId, communityId);\n const [openIds, counts, standings, subscribed] = await Promise.all([\n this.access.openSpaceIds(communityId, actor),\n this.repo.spaceMemberCounts(orgId, communityId, excluding),\n this.access.spaceStandings(communityId, actor),\n this.subscriptions.viewerSubscriptions(\n orgId,\n actor.id,\n 'space',\n spaces.map((space) => space.id),\n new Date(),\n ),\n ]);\n const open = new Set(openIds);\n const seen = spaces.filter((space) =>\n seesSpace(everySpace, space.accessRule, standings[space.id]),\n );\n return Promise.all(\n seen.map(async (space): Promise<CommunityHomeSpace> => {\n const memberCount = counts[space.id] ?? 0;\n const base = {\n id: space.id,\n name: space.name,\n memberCount,\n accessRule: space.accessRule,\n standing: standingOf(space, open.has(space.id), memberCount, standings[space.id]),\n coverUrl: images ? space.coverUrl : null,\n backgroundUrl: images ? space.backgroundUrl : null,\n iconUrl: images ? space.iconUrl : null,\n };\n if (!open.has(space.id)) {\n return { ...base, locked: true };\n }\n return {\n ...base,\n locked: false,\n description: space.description,\n capacity: space.capacity,\n settings: space.settings,\n topics: space.topics.map((topic) => ({ id: topic.id, name: topic.name })),\n links: await this.linkViews(actor, space.links),\n subscription: subscribed[space.id] ?? null,\n };\n }),\n );\n }\n\n private async linkViews(actor: Actor, links: SpaceDetail['links']): Promise<CommunityHomeLink[]> {\n const system = this.system(actor);\n const shown = (type: keyof typeof READ_OF, id: string, status: string) =>\n status === 'published' || actor.permissions.has(READ_OF[type], id);\n const views = await Promise.all(\n links.map(async (link): Promise<CommunityHomeLink | null> => {\n const course = await this.content.findCourse(link.contentId, system);\n if (course) {\n return shown('course', link.contentId, course.status)\n ? { id: link.id, contentId: link.contentId, type: 'course', title: course.title }\n : null;\n }\n const download = await this.content.findDownload(link.contentId, system);\n if (download) {\n return shown('download', link.contentId, download.status)\n ? { id: link.id, contentId: link.contentId, type: 'download', title: download.title }\n : null;\n }\n const community = await this.content.findCommunity(link.contentId, system);\n if (community) {\n return shown('community', link.contentId, community.status)\n ? { id: link.id, contentId: link.contentId, type: 'community', title: community.title }\n : null;\n }\n return null;\n }),\n );\n return views.filter((view): view is CommunityHomeLink => view !== null);\n }\n}\n\nfunction showsImages(status: string, communityId: string, actor: Actor): boolean {\n return status === 'published' || actor.permissions.has('communities:read', communityId);\n}\n\nfunction standingOf(\n space: SpaceDetail,\n open: boolean,\n memberCount: number,\n status: SpaceMember['status'] | undefined,\n): CommunityHomeSpace['standing'] {\n if (space.accessRule === 'all_members' || (open && status !== 'active')) {\n return 'open';\n }\n if (status === 'active') {\n return 'member';\n }\n if (status === 'requested' || status === 'invited') {\n return status;\n }\n if (space.capacity !== null && memberCount >= space.capacity) {\n return 'full';\n }\n return 'none';\n}\n"],"mappings":"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"}
|
package/dist/types/ports.d.ts
CHANGED
|
@@ -34,7 +34,9 @@ interface StoredObjectInfo {
|
|
|
34
34
|
}
|
|
35
35
|
interface PresignDownloadInput {
|
|
36
36
|
key: string;
|
|
37
|
-
|
|
37
|
+
/** Links signed with the same key, times and filename are identical. */
|
|
38
|
+
signedAt: Date;
|
|
39
|
+
expiresAt: Date;
|
|
38
40
|
/** Force a download with this filename (Content-Disposition: attachment). */
|
|
39
41
|
downloadFilename?: string;
|
|
40
42
|
}
|
|
@@ -46,7 +48,7 @@ interface ObjectStorage {
|
|
|
46
48
|
contentType?: string;
|
|
47
49
|
expiresInSeconds?: number;
|
|
48
50
|
}): Promise<PresignedUpload>;
|
|
49
|
-
/**
|
|
51
|
+
/** Signed URL to a private object, served with PUBLIC_CACHE_CONTROL. */
|
|
50
52
|
presignDownload(input: PresignDownloadInput): Promise<string>;
|
|
51
53
|
/** Object metadata, or null if it does not exist. */
|
|
52
54
|
stat(key: string): Promise<StoredObjectInfo | null>;
|
package/dist/types/ports.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ports.js","names":[],"sources":["../../src/types/ports.ts"],"sourcesContent":["// Deployment-swappable ports, implemented by @headless-lms/adapter-* packages.\n\nexport interface Logger {\n debug(msg: string, meta?: Record<string, unknown>): void;\n info(msg: string, meta?: Record<string, unknown>): void;\n warn(msg: string, meta?: Record<string, unknown>): void;\n error(msg: string, meta?: Record<string, unknown>): void;\n /** A logger whose every entry carries `bindings` (call-site meta wins on key clash). */\n child(bindings: Record<string, unknown>): Logger;\n}\n\nexport interface EmailMessage {\n to: string;\n subject: string;\n text: string;\n /** Optional rendered HTML body; transports fall back to text when absent. */\n html?: string;\n}\n\nexport interface EmailSender {\n send(message: EmailMessage): Promise<void>;\n}\n\n// --- Object storage (S3-compatible, opaque keys) ----------------------------\n\n/** A presigned URL the browser uses to PUT a file straight to the store. */\nexport interface PresignedUpload {\n url: string;\n method: 'PUT';\n key: string;\n expiresInSeconds: number;\n /** Headers the client must send on the PUT (e.g. Content-Type). */\n headers: Record<string, string>;\n}\n\nexport interface StoredObjectInfo {\n key: string;\n size: number;\n contentType?: string;\n lastModified?: string;\n}\n\nexport interface PresignDownloadInput {\n key: string;\n
|
|
1
|
+
{"version":3,"file":"ports.js","names":[],"sources":["../../src/types/ports.ts"],"sourcesContent":["// Deployment-swappable ports, implemented by @headless-lms/adapter-* packages.\n\nexport interface Logger {\n debug(msg: string, meta?: Record<string, unknown>): void;\n info(msg: string, meta?: Record<string, unknown>): void;\n warn(msg: string, meta?: Record<string, unknown>): void;\n error(msg: string, meta?: Record<string, unknown>): void;\n /** A logger whose every entry carries `bindings` (call-site meta wins on key clash). */\n child(bindings: Record<string, unknown>): Logger;\n}\n\nexport interface EmailMessage {\n to: string;\n subject: string;\n text: string;\n /** Optional rendered HTML body; transports fall back to text when absent. */\n html?: string;\n}\n\nexport interface EmailSender {\n send(message: EmailMessage): Promise<void>;\n}\n\n// --- Object storage (S3-compatible, opaque keys) ----------------------------\n\n/** A presigned URL the browser uses to PUT a file straight to the store. */\nexport interface PresignedUpload {\n url: string;\n method: 'PUT';\n key: string;\n expiresInSeconds: number;\n /** Headers the client must send on the PUT (e.g. Content-Type). */\n headers: Record<string, string>;\n}\n\nexport interface StoredObjectInfo {\n key: string;\n size: number;\n contentType?: string;\n lastModified?: string;\n}\n\nexport interface PresignDownloadInput {\n key: string;\n /** Links signed with the same key, times and filename are identical. */\n signedAt: Date;\n expiresAt: Date;\n /** Force a download with this filename (Content-Disposition: attachment). */\n downloadFilename?: string;\n}\n\nexport interface ObjectStorage {\n readonly uploadExpirySeconds: number;\n /** Presigned URL for a direct browser upload (temporary, expiring). */\n presignUpload(input: {\n key: string;\n contentType?: string;\n expiresInSeconds?: number;\n }): Promise<PresignedUpload>;\n /** Signed URL to a private object, served with PUBLIC_CACHE_CONTROL. */\n presignDownload(input: PresignDownloadInput): Promise<string>;\n /** Object metadata, or null if it does not exist. */\n stat(key: string): Promise<StoredObjectInfo | null>;\n list(prefix: string): Promise<StoredObjectInfo[]>;\n copy(input: { from: string; to: string }): Promise<void>;\n publish(input: { from: string; to: string }): Promise<void>;\n publicUrl(key: string): string;\n /** Permanently remove an object. */\n remove(key: string): Promise<void>;\n}\n\nexport const PUBLIC_PREFIX = 'public/';\nexport const PUBLIC_CACHE_CONTROL = 'public, max-age=31536000, immutable';\n"],"mappings":";AAuEA,MAAa,gBAAgB;AAC7B,MAAa,uBAAuB"}
|