@headless-lms/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/events.d.ts +24 -18
- package/dist/assets/file-urls.d.ts +11 -7
- package/dist/assets/file-urls.js +14 -4
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +5 -4
- package/dist/assets/index.js +4 -3
- package/dist/assets/jobs.d.ts +15 -0
- package/dist/assets/jobs.js +30 -0
- package/dist/assets/jobs.js.map +1 -0
- package/dist/assets/ports.d.ts +37 -17
- package/dist/assets/service.d.ts +30 -15
- package/dist/assets/service.js +229 -59
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js +2 -63
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.d.ts +1 -2
- package/dist/automations/catalog.js +137 -65
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +20 -20
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +36 -20
- package/dist/automations/community-mail.js +32 -24
- package/dist/automations/community-mail.js.map +1 -1
- package/dist/automations/event-permissions.d.ts +12 -0
- package/dist/automations/event-permissions.js +38 -0
- package/dist/automations/event-permissions.js.map +1 -0
- package/dist/automations/events.d.ts +0 -10
- package/dist/automations/index.d.ts +12 -4
- package/dist/automations/index.js +9 -1
- package/dist/automations/notifications/applicable.d.ts +8 -0
- package/dist/automations/notifications/applicable.js +12 -0
- package/dist/automations/notifications/applicable.js.map +1 -0
- package/dist/automations/notifications/debounce.d.ts +25 -0
- package/dist/automations/notifications/debounce.js +93 -0
- package/dist/automations/notifications/debounce.js.map +1 -0
- package/dist/automations/notifications/defaults.d.ts +8 -0
- package/dist/automations/notifications/defaults.js +346 -0
- package/dist/automations/notifications/defaults.js.map +1 -0
- package/dist/automations/notifications/delivery.d.ts +28 -0
- package/dist/automations/notifications/delivery.js +88 -0
- package/dist/automations/notifications/delivery.js.map +1 -0
- package/dist/automations/notifications/processor.d.ts +64 -0
- package/dist/automations/notifications/processor.js +270 -0
- package/dist/automations/notifications/processor.js.map +1 -0
- package/dist/automations/notifications/rules/content.d.ts +6 -0
- package/dist/automations/notifications/rules/content.js +96 -0
- package/dist/automations/notifications/rules/content.js.map +1 -0
- package/dist/automations/notifications/rules/discussion.d.ts +7 -0
- package/dist/automations/notifications/rules/discussion.js +678 -0
- package/dist/automations/notifications/rules/discussion.js.map +1 -0
- package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
- package/dist/automations/notifications/rules/entitlements.js +276 -0
- package/dist/automations/notifications/rules/entitlements.js.map +1 -0
- package/dist/automations/notifications/rules/events.d.ts +9 -0
- package/dist/automations/notifications/rules/events.js +27 -0
- package/dist/automations/notifications/rules/events.js.map +1 -0
- package/dist/automations/notifications/rules/index.d.ts +6 -0
- package/dist/automations/notifications/rules/index.js +15 -0
- package/dist/automations/notifications/rules/index.js.map +1 -0
- package/dist/automations/notifications/rules/organizations.d.ts +6 -0
- package/dist/automations/notifications/rules/organizations.js +35 -0
- package/dist/automations/notifications/rules/organizations.js.map +1 -0
- package/dist/automations/notifications/rules/recipients.d.ts +32 -0
- package/dist/automations/notifications/rules/recipients.js +98 -0
- package/dist/automations/notifications/rules/recipients.js.map +1 -0
- package/dist/automations/notifications/service.d.ts +29 -0
- package/dist/automations/notifications/service.js +45 -0
- package/dist/automations/notifications/service.js.map +1 -0
- package/dist/automations/notifications/types.d.ts +7 -0
- package/dist/automations/notifications/types.js +560 -0
- package/dist/automations/notifications/types.js.map +1 -0
- package/dist/automations/ports.d.ts +134 -21
- package/dist/automations/service.d.ts +30 -30
- package/dist/automations/service.js +111 -144
- package/dist/automations/service.js.map +1 -1
- package/dist/content/event-sight.d.ts +11 -0
- package/dist/content/event-sight.js +11 -0
- package/dist/content/event-sight.js.map +1 -0
- package/dist/content/events.d.ts +437 -46
- package/dist/content/events.js +45 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +8 -4
- package/dist/content/index.js +5 -1
- package/dist/content/jobs.d.ts +16 -0
- package/dist/content/jobs.js +37 -0
- package/dist/content/jobs.js.map +1 -0
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +37 -0
- package/dist/content/notification-categories.js +73 -0
- package/dist/content/notification-categories.js.map +1 -0
- package/dist/content/ports.d.ts +132 -68
- package/dist/content/service.d.ts +100 -68
- package/dist/content/service.js +703 -146
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +25 -0
- package/dist/content/titles.js +82 -0
- package/dist/content/titles.js.map +1 -0
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +4 -2
- package/dist/discussion/authors.js +9 -5
- package/dist/discussion/authors.js.map +1 -1
- package/dist/discussion/chats.d.ts +63 -39
- package/dist/discussion/chats.js +455 -190
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/digests.d.ts +24 -0
- package/dist/discussion/digests.js +104 -0
- package/dist/discussion/digests.js.map +1 -0
- package/dist/discussion/events.d.ts +227 -23
- package/dist/discussion/events.js +114 -19
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +12 -4
- package/dist/discussion/index.js +9 -2
- package/dist/discussion/jobs.d.ts +16 -0
- package/dist/discussion/jobs.js +40 -0
- package/dist/discussion/jobs.js.map +1 -0
- package/dist/discussion/live.d.ts +106 -0
- package/dist/discussion/live.js +642 -0
- package/dist/discussion/live.js.map +1 -0
- package/dist/discussion/mentions.d.ts +11 -0
- package/dist/discussion/mentions.js +35 -0
- package/dist/discussion/mentions.js.map +1 -0
- package/dist/discussion/ports.d.ts +279 -114
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +60 -44
- package/dist/discussion/posts.js +453 -287
- package/dist/discussion/posts.js.map +1 -1
- package/dist/discussion/rules.d.ts +18 -0
- package/dist/discussion/rules.js +22 -0
- package/dist/discussion/rules.js.map +1 -0
- package/dist/discussion/service.d.ts +50 -35
- package/dist/discussion/service.js +333 -205
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +37 -0
- package/dist/discussion/subscriptions.js +115 -0
- package/dist/discussion/subscriptions.js.map +1 -0
- package/dist/discussion/titles.d.ts +28 -0
- package/dist/discussion/titles.js +98 -0
- package/dist/discussion/titles.js.map +1 -0
- package/dist/entitlements/events.d.ts +20 -20
- package/dist/entitlements/index.d.ts +5 -4
- package/dist/entitlements/index.js +3 -2
- package/dist/entitlements/jobs.d.ts +12 -0
- package/dist/entitlements/jobs.js +18 -0
- package/dist/entitlements/jobs.js.map +1 -0
- package/dist/entitlements/model.d.ts +2 -2
- package/dist/entitlements/ports.d.ts +68 -38
- package/dist/entitlements/rules.d.ts +3 -6
- package/dist/entitlements/rules.js +5 -10
- package/dist/entitlements/rules.js.map +1 -1
- package/dist/entitlements/service.d.ts +43 -28
- package/dist/entitlements/service.js +387 -160
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +30 -0
- package/dist/identity/devices.js +109 -0
- package/dist/identity/devices.js.map +1 -0
- package/dist/identity/index.d.ts +6 -5
- package/dist/identity/index.js +4 -3
- package/dist/identity/model.d.ts +2 -2
- package/dist/identity/notification-settings.d.ts +28 -0
- package/dist/identity/notification-settings.js +114 -0
- package/dist/identity/notification-settings.js.map +1 -0
- package/dist/identity/ports.d.ts +80 -56
- package/dist/identity/service.d.ts +8 -7
- package/dist/identity/service.js +17 -13
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/ports.d.ts +9 -8
- package/dist/integrations/service.d.ts +9 -8
- package/dist/integrations/service.js +33 -16
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/default-roles.d.ts +22 -0
- package/dist/organizations/default-roles.js +89 -0
- package/dist/organizations/default-roles.js.map +1 -0
- package/dist/organizations/effective-permissions.d.ts +45 -0
- package/dist/organizations/effective-permissions.js +113 -0
- package/dist/organizations/effective-permissions.js.map +1 -0
- package/dist/organizations/events.d.ts +320 -181
- package/dist/organizations/events.js +90 -18
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +15 -10
- package/dist/organizations/index.js +8 -3
- package/dist/organizations/members.d.ts +66 -8
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/notification-catalog.d.ts +31 -0
- package/dist/organizations/notification-catalog.js +32 -0
- package/dist/organizations/notification-catalog.js.map +1 -0
- package/dist/organizations/notification-categories.d.ts +34 -0
- package/dist/organizations/notification-categories.js +66 -0
- package/dist/organizations/notification-categories.js.map +1 -0
- package/dist/organizations/org-roles.d.ts +33 -0
- package/dist/organizations/org-roles.js +49 -0
- package/dist/organizations/org-roles.js.map +1 -0
- package/dist/organizations/permissions.d.ts +311 -0
- package/dist/organizations/permissions.js +327 -0
- package/dist/organizations/permissions.js.map +1 -0
- package/dist/organizations/ports.d.ts +187 -65
- package/dist/organizations/service.d.ts +104 -35
- package/dist/organizations/service.js +1247 -323
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/testing.d.ts +18 -0
- package/dist/organizations/testing.js +37 -0
- package/dist/organizations/testing.js.map +1 -0
- package/dist/organizations/types.d.ts +5 -16
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +11 -3
- package/dist/progress/service.d.ts +6 -5
- package/dist/progress/service.js +23 -8
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +6 -4
- package/dist/reporting/admin/model.d.ts +36 -3
- package/dist/reporting/admin/ports.d.ts +39 -14
- package/dist/reporting/admin/service.d.ts +32 -11
- package/dist/reporting/admin/service.js +102 -18
- package/dist/reporting/admin/service.js.map +1 -1
- package/dist/reporting/events/index.d.ts +27 -0
- package/dist/reporting/events/index.js +20 -0
- package/dist/reporting/events/index.js.map +1 -0
- package/dist/reporting/member/calendar.d.ts +11 -0
- package/dist/reporting/member/calendar.js +75 -0
- package/dist/reporting/member/calendar.js.map +1 -0
- package/dist/reporting/member/index.d.ts +3 -2
- package/dist/reporting/member/model.d.ts +80 -26
- package/dist/reporting/member/ports.d.ts +50 -28
- package/dist/reporting/member/service.d.ts +81 -35
- package/dist/reporting/member/service.js +438 -145
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +22 -3
- package/dist/shared/errors.d.ts +15 -2
- package/dist/shared/errors.js +21 -2
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +7 -0
- package/dist/shared/id.js +8 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/logger.d.ts +2 -1
- package/dist/shared/logger.js +10 -1
- package/dist/shared/logger.js.map +1 -1
- package/dist/shared/mailer.d.ts +11 -1
- package/dist/shared/mailer.js +32 -5
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/notification-categories.d.ts +16 -0
- package/dist/shared/notification-categories.js +94 -0
- package/dist/shared/notification-categories.js.map +1 -0
- package/dist/shared/notification-settings.d.ts +14 -0
- package/dist/shared/notification-settings.js +61 -0
- package/dist/shared/notification-settings.js.map +1 -0
- package/dist/shared/ports.d.ts +68 -2
- package/dist/shared/settings.d.ts +2 -1
- package/dist/shared/settings.js +1 -0
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/automations.d.ts +8 -2
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +4 -5
- package/dist/types/email-templates.d.ts +119 -6
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +14 -17
- package/dist/types/index.d.ts +13 -12
- package/dist/types/index.js +2 -1
- package/dist/types/notifications.d.ts +157 -0
- package/dist/types/notifications.js +1 -0
- package/dist/types/organizations.d.ts +3 -2
- package/dist/types/ports.d.ts +8 -1
- package/dist/types/ports.js +7 -1
- package/dist/types/ports.js.map +1 -0
- package/dist/types/schemas/assets.d.ts +5 -5
- package/dist/types/schemas/assets.js +4 -3
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +1 -10
- package/dist/types/schemas/automations.js +1 -3
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +596 -11
- package/dist/types/schemas/content.js +225 -9
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +362 -66
- package/dist/types/schemas/discussion.js +123 -15
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +14 -1
- package/dist/types/schemas/entitlements.js +11 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +1 -85
- package/dist/types/schemas/identity.js +1 -42
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +8 -7
- package/dist/types/schemas/index.js +8 -7
- package/dist/types/schemas/notifications.d.ts +96 -0
- package/dist/types/schemas/notifications.js +57 -0
- package/dist/types/schemas/notifications.js.map +1 -0
- package/dist/types/schemas/organizations.d.ts +96 -60
- package/dist/types/schemas/organizations.js +78 -27
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +0 -1
- package/dist/types/schemas/progress.js +0 -1
- package/dist/types/schemas/progress.js.map +1 -1
- package/package.json +1 -1
- package/dist/automations/community-notifications.d.ts +0 -16
- package/dist/automations/community-notifications.js +0 -84
- package/dist/automations/community-notifications.js.map +0 -1
- package/dist/automations/seeds.d.ts +0 -17
- package/dist/automations/seeds.js +0 -235
- package/dist/automations/seeds.js.map +0 -1
- package/dist/identity/notifications.d.ts +0 -27
- package/dist/identity/notifications.js +0 -101
- package/dist/identity/notifications.js.map +0 -1
- package/dist/organizations/access-scopes.d.ts +0 -19
- package/dist/organizations/access-scopes.js +0 -89
- package/dist/organizations/access-scopes.js.map +0 -1
- package/dist/organizations/roles.d.ts +0 -19
- package/dist/organizations/roles.js +0 -82
- package/dist/organizations/roles.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../../src/reporting/member/service.ts"],"sourcesContent":["import { type ContentServicePort, type SpaceDetail } from '../../content/index.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 { isStaffRole } from '../../organizations/index.js';\nimport type { ChatService, PostService } from '../../discussion/index.js';\nimport type { Notification, NotificationInbox } from '../../identity/index.js';\nimport type { Actor } from '../../shared/actor.js';\nimport type { Page } 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 Module,\n NotificationView,\n NotificationsView,\n} from './model.js';\nimport type { MemberReportRepository, MemberReportService } from './ports.js';\n\nfunction ref(\n id: string | null,\n names: ReadonlyMap<string, string>,\n): { id: string; name: string } | null {\n const name = id ? names.get(id) : undefined;\n return id && name !== undefined ? { id, name } : null;\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' | 'openSpaceIds' | 'spaceStandings' | 'hasAccess' | 'listAccessibleContent'\n>;\n\nexport type MemberReportServiceParams = {\n repo: MemberReportRepository;\n content: ContentServicePort;\n progress: ProgressService;\n files: Pick<AssetFiles, 'signFile'>;\n fileUrls: FileUrls;\n access: MemberAccess;\n notifications: Pick<NotificationInbox, 'listNotifications'>;\n posts: Pick<PostService, 'visibleTitles'>;\n chats: Pick<ChatService, 'titlesFor'>;\n};\n\nexport class MemberReportServiceImpl implements MemberReportService {\n private readonly repo: MemberReportRepository;\n private readonly content: ContentServicePort;\n private readonly progress: ProgressService;\n private readonly files: Pick<AssetFiles, 'signFile'>;\n private readonly fileUrls: FileUrls;\n private readonly access: MemberAccess;\n private readonly inbox: Pick<NotificationInbox, 'listNotifications'>;\n private readonly posts: Pick<PostService, 'visibleTitles'>;\n private readonly chats: Pick<ChatService, 'titlesFor'>;\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.inbox = params.notifications;\n this.posts = params.posts;\n this.chats = params.chats;\n }\n\n async notifications(\n orgId: string,\n actor: Actor,\n userId: string,\n query: { page: number; pageSize: number },\n ): Promise<NotificationsView> {\n const page = await this.inbox.listNotifications(orgId, userId, query);\n return {\n rows: await this.describeNotifications(orgId, actor, page.rows),\n total: page.total,\n page: page.page,\n pageSize: page.pageSize,\n unreadCount: page.unreadCount,\n };\n }\n\n async notificationView(\n orgId: string,\n actor: Actor,\n notification: Notification,\n ): Promise<NotificationView> {\n const [view] = await this.describeNotifications(orgId, actor, [notification]);\n return view!;\n }\n\n private async describeNotifications(\n orgId: string,\n actor: Actor,\n rows: Notification[],\n ): Promise<NotificationView[]> {\n const ids = (pick: (row: Notification) => string | null): string[] => [\n ...new Set(rows.flatMap((row) => pick(row) ?? [])),\n ];\n const [people, postTitles, chatTitles, spaces, communities] = await Promise.all([\n this.repo.people(\n orgId,\n ids((row) => row.actorOrgUserId),\n ),\n this.posts.visibleTitles(\n orgId,\n actor,\n ids((row) => row.postId),\n ),\n this.chats.titlesFor(\n orgId,\n actor,\n ids((row) => row.chatId),\n ),\n Promise.all(ids((row) => row.spaceId).map((id) => this.content.getSpace(orgId, id))),\n Promise.all(ids((row) => row.communityId).map((id) => this.content.getCommunity(orgId, id))),\n ]);\n const personById = new Map(people.map((person) => [person.orgUserId, person]));\n const spaceById = new Map(spaces.flatMap((space) => (space ? [[space.id, space.name]] : [])));\n const communityById = new Map(\n communities.flatMap((community) => (community ? [[community.id, community.title]] : [])),\n );\n const postById = new Map(Object.entries(postTitles));\n const chatById = new Map(Object.entries(chatTitles));\n return rows.map((row) => {\n const person = row.actorOrgUserId ? personById.get(row.actorOrgUserId) : undefined;\n const community = ref(row.communityId, communityById);\n const space = ref(row.spaceId, spaceById);\n const post = ref(row.postId, postById);\n const chat = ref(row.chatId, chatById);\n return {\n id: row.id,\n kind: row.kind,\n createdAt: row.createdAt,\n readAt: row.readAt,\n actor: person\n ? {\n orgUserId: person.orgUserId,\n displayName: person.displayName,\n imageUrl: person.imageAssetId ? this.fileUrls.personImage(person.orgUserId) : null,\n }\n : null,\n community: community && { id: community.id, title: community.name },\n space: space && { id: space.id, name: space.name },\n post: post && { id: post.id, title: post.name },\n commentId: row.commentId,\n chat: chat && { id: chat.id, title: chat.name },\n messageId: row.messageId,\n };\n });\n }\n\n async listCourses(orgId: string, orgUserId: string): Promise<CourseView[]> {\n const ids = await this.access.listAccessibleContent(orgId, orgUserId, 'course');\n const courses = await Promise.all(ids.map((id) => this.content.getCourse(orgId, id)));\n return courses.filter((c): c is CourseView => c !== null && c.status === 'published');\n }\n\n async getCourse(orgId: string, orgUserId: string, courseId: string): Promise<CourseView | null> {\n if (!(await this.access.hasAccess(orgId, orgUserId, courseId))) {\n return null;\n }\n const course = await this.content.getCourse(orgId, courseId);\n return course && course.status === 'published' ? course : null;\n }\n\n async listModules(orgId: string, orgUserId: string, courseId: string): Promise<Module[] | null> {\n if (!(await this.getCourse(orgId, orgUserId, courseId))) {\n return null;\n }\n return this.content.listCourseModules(orgId, courseId);\n }\n\n async listActivities(\n orgId: string,\n orgUserId: string,\n courseId: string,\n ): Promise<ActivityView[] | null> {\n if (!(await this.getCourse(orgId, orgUserId, courseId))) {\n return null;\n }\n const activities = await this.content.listCourseActivityViews(orgId, courseId);\n return activities.filter((a) => isActivityPublished(a.settings));\n }\n\n async courseProgress(\n orgId: string,\n orgUserId: string,\n courseId: string,\n ): Promise<CourseProgressView | null> {\n if (!(await this.getCourse(orgId, orgUserId, courseId))) {\n return null;\n }\n const courseActivities = await this.content.listCourseActivities(orgId, courseId);\n const ids = courseActivities.filter((a) => isActivityPublished(a.settings)).map((a) => a.id);\n const records = await this.progress.listByTargets(orgId, orgUserId, 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,\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(orgId: string, orgUserId: string): Promise<DownloadView[]> {\n const ids = await this.access.listAccessibleContent(orgId, orgUserId, 'download');\n const rows = await Promise.all(ids.map((id) => this.content.getDownload(orgId, id)));\n return rows.filter((d): d is DownloadView => d !== null && d.status === 'published');\n }\n\n async getDownload(\n orgId: string,\n orgUserId: string,\n downloadId: string,\n ): Promise<{ download: DownloadView; assets: DownloadFile[] } | null> {\n if (!(await this.access.hasAccess(orgId, orgUserId, downloadId))) {\n return null;\n }\n const download = await this.content.getDownload(orgId, downloadId);\n if (!download || download.status !== 'published') {\n return null;\n }\n const assets = await this.content.listDownloadAssets(orgId, downloadId);\n return { download, assets };\n }\n\n async thumbnailFile(\n orgId: string,\n actor: Actor,\n owner: 'course' | 'download' | 'community',\n id: string,\n ): Promise<string | null> {\n const item =\n owner === 'course'\n ? await this.content.getCourse(orgId, id)\n : owner === 'download'\n ? await this.content.getDownload(orgId, id)\n : await this.content.getCommunity(orgId, id);\n if (!item?.thumbnailAssetId || (item.status !== 'published' && !isStaffRole(actor.role))) {\n return null;\n }\n return this.files.signFile(orgId, item.thumbnailAssetId);\n }\n\n async activityFile(\n orgId: string,\n actor: Actor,\n activityId: string,\n assetId: string,\n ): Promise<string | null> {\n const activity = await this.content.getActivity(orgId, activityId);\n if (!activity) {\n return null;\n }\n if (!isStaffRole(actor.role)) {\n if (\n !isActivityPublished(activity.settings) ||\n !(await this.getCourse(orgId, actor.id, activity.courseId))\n ) {\n return null;\n }\n }\n const links = await this.content.listCourseActivityAssets(orgId, activity.courseId);\n if (!links.some((link) => link.activityId === activityId && link.assetId === assetId)) {\n return null;\n }\n return this.files.signFile(orgId, assetId);\n }\n\n async downloadFile(\n orgId: string,\n actor: Actor,\n downloadId: string,\n assetId: string,\n ): Promise<string | null> {\n if (!isStaffRole(actor.role) && !(await this.getDownload(orgId, actor.id, downloadId))) {\n return null;\n }\n const files = await this.content.listDownloadAssets(orgId, downloadId);\n const file = files.find((f) => f.assetId === assetId);\n if (!file) {\n return null;\n }\n return this.files.signFile(orgId, assetId, { saveAs: file.displayName ?? file.filename });\n }\n\n async personImage(orgId: string, orgUserId: string): Promise<string | null> {\n const [person] = await this.repo.people(orgId, [orgUserId]);\n if (!person?.imageAssetId) {\n return null;\n }\n return this.files.signFile(orgId, person.imageAssetId);\n }\n\n async directory(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: DirectoryQuery,\n ): Promise<Page<DirectoryEntry> | null> {\n if (!(await this.access.isCommunityMember(orgId, actor, communityId))) {\n return null;\n }\n const community = await this.content.getCommunity(orgId, communityId);\n if (!community) {\n return null;\n }\n const page = await this.repo.directory(orgId, communityId, query);\n return {\n ...page,\n rows: page.rows.map((row) => ({\n ...row,\n imageUrl: row.imageAssetId ? this.fileUrls.personImage(row.orgUserId) : null,\n })),\n };\n }\n\n async communityHome(\n orgId: string,\n actor: Actor,\n communityId: string,\n ): Promise<CommunityHome | null> {\n if (!(await this.access.isCommunityMember(orgId, actor, communityId))) {\n return null;\n }\n const community = await this.content.getCommunity(orgId, communityId);\n if (!community) {\n return null;\n }\n const spaces = await this.content.listSpaces(orgId, communityId);\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(orgId, actor, communityId, spaces),\n };\n }\n\n async communitySpace(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<CommunityHomeSpace | null> {\n if (!(await this.access.isCommunityMember(orgId, actor, communityId))) {\n return null;\n }\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space || space.communityId !== communityId) {\n return null;\n }\n const [view] = await this.homeSpaces(orgId, actor, communityId, [space]);\n return view ?? null;\n }\n\n private async homeSpaces(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaces: SpaceDetail[],\n ): Promise<CommunityHomeSpace[]> {\n const [openIds, counts, standings] = await Promise.all([\n this.access.openSpaceIds(orgId, actor, communityId),\n this.repo.spaceMemberCounts(orgId, communityId),\n this.access.spaceStandings(orgId, actor, communityId),\n ]);\n const open = new Set(openIds);\n const seen = spaces.filter((space) =>\n seesSpace(actor.role, 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 };\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(orgId, actor, space.links),\n };\n }),\n );\n }\n\n private async linkViews(\n orgId: string,\n actor: Actor,\n links: SpaceDetail['links'],\n ): Promise<CommunityHomeLink[]> {\n const shown = (status: string) => status === 'published' || isStaffRole(actor.role);\n const views = await Promise.all(\n links.map(async (link): Promise<CommunityHomeLink | null> => {\n const course = await this.content.getCourse(orgId, link.contentId);\n if (course) {\n return shown(course.status)\n ? { id: link.id, contentId: link.contentId, type: 'course', title: course.title }\n : null;\n }\n const download = await this.content.getDownload(orgId, link.contentId);\n if (download) {\n return shown(download.status)\n ? { id: link.id, contentId: link.contentId, type: 'download', title: download.title }\n : null;\n }\n const community = await this.content.getCommunity(orgId, link.contentId);\n if (community) {\n return shown(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 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} 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 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 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: SpaceDetail[],\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 };\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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"}
|
package/dist/shared/actor.d.ts
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Permission } from "../types/schemas/organizations.js";
|
|
2
|
+
import { ContentId } from "../types/organizations.js";
|
|
3
|
+
import "../types/index.js";
|
|
2
4
|
//#region src/shared/actor.d.ts
|
|
5
|
+
interface RoleRef {
|
|
6
|
+
key: string;
|
|
7
|
+
name: string;
|
|
8
|
+
team: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface EffectivePermissions {
|
|
11
|
+
has(permission: Permission, on?: ContentId): boolean;
|
|
12
|
+
require(permission: Permission, on?: ContentId): void;
|
|
13
|
+
anywhere(permission: Permission): boolean;
|
|
14
|
+
where(permission: Permission): {
|
|
15
|
+
org: boolean;
|
|
16
|
+
content: ContentId[];
|
|
17
|
+
};
|
|
18
|
+
roleOn(on?: ContentId): RoleRef;
|
|
19
|
+
isOwner(): boolean;
|
|
20
|
+
}
|
|
3
21
|
interface Actor {
|
|
4
22
|
id: string;
|
|
5
|
-
|
|
23
|
+
orgId: string;
|
|
24
|
+
permissions: EffectivePermissions;
|
|
6
25
|
}
|
|
7
26
|
//#endregion
|
|
8
|
-
export { Actor };
|
|
27
|
+
export { Actor, EffectivePermissions, RoleRef };
|
|
9
28
|
//# sourceMappingURL=actor.d.ts.map
|
package/dist/shared/errors.d.ts
CHANGED
|
@@ -10,7 +10,8 @@ declare class NotFoundError extends Error {
|
|
|
10
10
|
/** A command conflicts with existing state (duplicate email, already linked,
|
|
11
11
|
* …). The HTTP layer maps this to 409. */
|
|
12
12
|
declare class ConflictError extends Error {
|
|
13
|
-
|
|
13
|
+
readonly code?: string | undefined;
|
|
14
|
+
constructor(message: string, code?: string | undefined);
|
|
14
15
|
}
|
|
15
16
|
/** The caller is authenticated but not permitted to perform this command.
|
|
16
17
|
* The HTTP layer maps this to 403. */
|
|
@@ -23,6 +24,18 @@ declare class ForbiddenError extends Error {
|
|
|
23
24
|
declare class InvalidInputError extends Error {
|
|
24
25
|
constructor(message: string);
|
|
25
26
|
}
|
|
27
|
+
declare class UnavailableError extends Error {
|
|
28
|
+
readonly details?: Record<string, string> | undefined;
|
|
29
|
+
constructor(message: string, details?: Record<string, string> | undefined);
|
|
30
|
+
}
|
|
31
|
+
type ValidationProblem = {
|
|
32
|
+
path?: string;
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
35
|
+
declare class ValidationError extends Error {
|
|
36
|
+
readonly problems: ValidationProblem[];
|
|
37
|
+
constructor(problems: string | ValidationProblem[]);
|
|
38
|
+
}
|
|
26
39
|
//#endregion
|
|
27
|
-
export { ConflictError, ForbiddenError, InvalidInputError, NotFoundError };
|
|
40
|
+
export { ConflictError, ForbiddenError, InvalidInputError, NotFoundError, UnavailableError, ValidationError, ValidationProblem };
|
|
28
41
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/shared/errors.js
CHANGED
|
@@ -15,8 +15,10 @@ var NotFoundError = class extends Error {
|
|
|
15
15
|
/** A command conflicts with existing state (duplicate email, already linked,
|
|
16
16
|
* …). The HTTP layer maps this to 409. */
|
|
17
17
|
var ConflictError = class extends Error {
|
|
18
|
-
|
|
18
|
+
code;
|
|
19
|
+
constructor(message, code) {
|
|
19
20
|
super(message);
|
|
21
|
+
this.code = code;
|
|
20
22
|
this.name = "ConflictError";
|
|
21
23
|
}
|
|
22
24
|
};
|
|
@@ -37,7 +39,24 @@ var InvalidInputError = class extends Error {
|
|
|
37
39
|
this.name = "InvalidInputError";
|
|
38
40
|
}
|
|
39
41
|
};
|
|
42
|
+
var UnavailableError = class extends Error {
|
|
43
|
+
details;
|
|
44
|
+
constructor(message, details) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.details = details;
|
|
47
|
+
this.name = "UnavailableError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var ValidationError = class extends Error {
|
|
51
|
+
problems;
|
|
52
|
+
constructor(problems) {
|
|
53
|
+
const list = typeof problems === "string" ? [{ message: problems }] : problems;
|
|
54
|
+
super(list.map((problem) => problem.path ? `${problem.path}: ${problem.message}` : problem.message).join("; "));
|
|
55
|
+
this.problems = list;
|
|
56
|
+
this.name = "ValidationError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
40
59
|
//#endregion
|
|
41
|
-
export { ConflictError, ForbiddenError, InvalidInputError, NotFoundError };
|
|
60
|
+
export { ConflictError, ForbiddenError, InvalidInputError, NotFoundError, UnavailableError, ValidationError };
|
|
42
61
|
|
|
43
62
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../src/shared/errors.ts"],"sourcesContent":["// Cross-cutting domain errors. Like `../shared/ports.js`, this file is\n// importable from any context; the central HTTP error handler maps these to\n// status codes so routes never hand-roll error replies.\n\n/** A command's target does not exist in this org. Queries return null for the\n * same situation — a missing row is a normal answer to a question, but a\n * failed precondition for a command. */\nexport class NotFoundError extends Error {\n constructor(\n readonly resource: string,\n readonly id: string,\n ) {\n super(`${resource} not found`);\n this.name = 'NotFoundError';\n }\n}\n\n/** A command conflicts with existing state (duplicate email, already linked,\n * …). The HTTP layer maps this to 409. */\nexport class ConflictError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConflictError';\n }\n}\n\n/** The caller is authenticated but not permitted to perform this command.\n * The HTTP layer maps this to 403. */\nexport class ForbiddenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ForbiddenError';\n }\n}\n\n/** The caller's own input breaks a rule, independent of existing state\n * (wrong file type, too large, talking to yourself, …). The HTTP layer maps\n * this to 400. */\nexport class InvalidInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidInputError';\n }\n}\n"],"mappings":";;;;AAOA,IAAa,gBAAb,cAAmC,MAAM;CAE5B;CACA;CAFX,YACE,UACA,IACA;EACA,MAAM,GAAG,SAAS,WAAW;EAHpB,KAAA,WAAA;EACA,KAAA,KAAA;EAGT,KAAK,OAAO;CACd;AACF;;;AAIA,IAAa,gBAAb,cAAmC,MAAM;
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../src/shared/errors.ts"],"sourcesContent":["// Cross-cutting domain errors. Like `../shared/ports.js`, this file is\n// importable from any context; the central HTTP error handler maps these to\n// status codes so routes never hand-roll error replies.\n\n/** A command's target does not exist in this org. Queries return null for the\n * same situation — a missing row is a normal answer to a question, but a\n * failed precondition for a command. */\nexport class NotFoundError extends Error {\n constructor(\n readonly resource: string,\n readonly id: string,\n ) {\n super(`${resource} not found`);\n this.name = 'NotFoundError';\n }\n}\n\n/** A command conflicts with existing state (duplicate email, already linked,\n * …). The HTTP layer maps this to 409. */\nexport class ConflictError extends Error {\n constructor(\n message: string,\n readonly code?: string,\n ) {\n super(message);\n this.name = 'ConflictError';\n }\n}\n\n/** The caller is authenticated but not permitted to perform this command.\n * The HTTP layer maps this to 403. */\nexport class ForbiddenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ForbiddenError';\n }\n}\n\n/** The caller's own input breaks a rule, independent of existing state\n * (wrong file type, too large, talking to yourself, …). The HTTP layer maps\n * this to 400. */\nexport class InvalidInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidInputError';\n }\n}\n\nexport class UnavailableError extends Error {\n constructor(\n message: string,\n readonly details?: Record<string, string>,\n ) {\n super(message);\n this.name = 'UnavailableError';\n }\n}\n\nexport type ValidationProblem = { path?: string; message: string };\n\nexport class ValidationError extends Error {\n readonly problems: ValidationProblem[];\n\n constructor(problems: string | ValidationProblem[]) {\n const list = typeof problems === 'string' ? [{ message: problems }] : problems;\n super(\n list\n .map((problem) => (problem.path ? `${problem.path}: ${problem.message}` : problem.message))\n .join('; '),\n );\n this.problems = list;\n this.name = 'ValidationError';\n }\n}\n"],"mappings":";;;;AAOA,IAAa,gBAAb,cAAmC,MAAM;CAE5B;CACA;CAFX,YACE,UACA,IACA;EACA,MAAM,GAAG,SAAS,WAAW;EAHpB,KAAA,WAAA;EACA,KAAA,KAAA;EAGT,KAAK,OAAO;CACd;AACF;;;AAIA,IAAa,gBAAb,cAAmC,MAAM;CAG5B;CAFX,YACE,SACA,MACA;EACA,MAAM,OAAO;EAFJ,KAAA,OAAA;EAGT,KAAK,OAAO;CACd;AACF;;;AAIA,IAAa,iBAAb,cAAoC,MAAM;CACxC,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAEA,IAAa,mBAAb,cAAsC,MAAM;CAG/B;CAFX,YACE,SACA,SACA;EACA,MAAM,OAAO;EAFJ,KAAA,UAAA;EAGT,KAAK,OAAO;CACd;AACF;AAIA,IAAa,kBAAb,cAAqC,MAAM;CACzC;CAEA,YAAY,UAAwC;EAClD,MAAM,OAAO,OAAO,aAAa,WAAW,CAAC,EAAE,SAAS,SAAS,CAAC,IAAI;EACtE,MACE,KACG,KAAK,YAAa,QAAQ,OAAO,GAAG,QAAQ,KAAK,IAAI,QAAQ,YAAY,QAAQ,OAAQ,CAAC,CAC1F,KAAK,IAAI,CACd;EACA,KAAK,WAAW;EAChB,KAAK,OAAO;CACd;AACF"}
|
package/dist/shared/id.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ declare const ID_PREFIXES: {
|
|
|
12
12
|
readonly user: "usr";
|
|
13
13
|
readonly student: "stu";
|
|
14
14
|
readonly orgUser: "orm";
|
|
15
|
+
readonly orgRole: "orl";
|
|
16
|
+
readonly roleAssignment: "ras";
|
|
15
17
|
readonly invite: "ivt";
|
|
16
18
|
readonly bundle: "bun";
|
|
17
19
|
readonly course: "crs";
|
|
@@ -20,6 +22,8 @@ declare const ID_PREFIXES: {
|
|
|
20
22
|
readonly activityAsset: "aca";
|
|
21
23
|
readonly download: "dwn";
|
|
22
24
|
readonly downloadAsset: "dla";
|
|
25
|
+
readonly contentEvent: "evn";
|
|
26
|
+
readonly eventAsset: "eva";
|
|
23
27
|
readonly asset: "ast";
|
|
24
28
|
readonly entitlement: "ent";
|
|
25
29
|
readonly progress: "prg";
|
|
@@ -41,6 +45,9 @@ declare const ID_PREFIXES: {
|
|
|
41
45
|
readonly message: "msg";
|
|
42
46
|
readonly messageFlag: "mfl";
|
|
43
47
|
readonly notification: "ntf";
|
|
48
|
+
readonly notificationSetting: "nst";
|
|
49
|
+
readonly notificationDelivery: "ndl";
|
|
50
|
+
readonly device: "dev";
|
|
44
51
|
};
|
|
45
52
|
type IdType = keyof typeof ID_PREFIXES;
|
|
46
53
|
/** A bare KSUID body (27-char base62, time-sortable), no prefix. */
|
package/dist/shared/id.js
CHANGED
|
@@ -13,6 +13,8 @@ const ID_PREFIXES = {
|
|
|
13
13
|
user: "usr",
|
|
14
14
|
student: "stu",
|
|
15
15
|
orgUser: "orm",
|
|
16
|
+
orgRole: "orl",
|
|
17
|
+
roleAssignment: "ras",
|
|
16
18
|
invite: "ivt",
|
|
17
19
|
bundle: "bun",
|
|
18
20
|
course: "crs",
|
|
@@ -21,6 +23,8 @@ const ID_PREFIXES = {
|
|
|
21
23
|
activityAsset: "aca",
|
|
22
24
|
download: "dwn",
|
|
23
25
|
downloadAsset: "dla",
|
|
26
|
+
contentEvent: "evn",
|
|
27
|
+
eventAsset: "eva",
|
|
24
28
|
asset: "ast",
|
|
25
29
|
entitlement: "ent",
|
|
26
30
|
progress: "prg",
|
|
@@ -41,7 +45,10 @@ const ID_PREFIXES = {
|
|
|
41
45
|
chat: "cht",
|
|
42
46
|
message: "msg",
|
|
43
47
|
messageFlag: "mfl",
|
|
44
|
-
notification: "ntf"
|
|
48
|
+
notification: "ntf",
|
|
49
|
+
notificationSetting: "nst",
|
|
50
|
+
notificationDelivery: "ndl",
|
|
51
|
+
device: "dev"
|
|
45
52
|
};
|
|
46
53
|
/** A bare KSUID body (27-char base62, time-sortable), no prefix. */
|
|
47
54
|
function ksuid() {
|
package/dist/shared/id.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id.js","names":[],"sources":["../../src/shared/id.ts"],"sourcesContent":["import KSUID from 'ksuid';\n\n/**\n * Prefixed, KSUID-bodied string ids — `${prefix}_${ksuid}`, e.g. `org_2QZ4mK...`.\n *\n * The body is a real KSUID (27-char base62, 4-byte timestamp + 16 random bytes),\n * so ids are lexicographically time-sortable. The prefix marks the entity type at\n * a glance. A core utility (one implementation, nothing to swap), called directly\n * by core services and used as the Drizzle `.$defaultFn` for every domain id column.\n */\nexport const ID_PREFIXES = {\n organization: 'org',\n user: 'usr',\n student: 'stu',\n orgUser: 'orm',\n invite: 'ivt',\n bundle: 'bun',\n course: 'crs',\n module: 'mod',\n activity: 'act',\n activityAsset: 'aca',\n download: 'dwn',\n downloadAsset: 'dla',\n asset: 'ast',\n entitlement: 'ent',\n progress: 'prg',\n credential: 'crd',\n connection: 'con',\n event: 'evt',\n automation: 'atm',\n automationRun: 'run',\n comment: 'cmt',\n commentReport: 'crp',\n community: 'cmy',\n space: 'spc',\n topic: 'top',\n spaceLink: 'slk',\n spaceMember: 'spm',\n post: 'pst',\n postFlag: 'pfl',\n chat: 'cht',\n message: 'msg',\n messageFlag: 'mfl',\n notification: 'ntf',\n} as const;\n\nexport type IdType = keyof typeof ID_PREFIXES;\n\n/** A bare KSUID body (27-char base62, time-sortable), no prefix. */\nexport function ksuid(): string {\n return KSUID.randomSync().string;\n}\n\n/** Attach an arbitrary prefix to a fresh KSUID — `${prefix}_${ksuid}`. */\nexport function prefixId(prefix: string): string {\n return `${prefix}_${ksuid()}`;\n}\n\n/** Generate a prefixed, time-sortable id for the given domain entity type. */\nexport function genId(type: IdType): string {\n return prefixId(ID_PREFIXES[type]);\n}\n\n/** True when `value` carries the prefix for `type` (shape check only, not existence). */\nexport function isId(type: IdType, value: string): boolean {\n return value.startsWith(`${ID_PREFIXES[type]}_`);\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAa,cAAc;CACzB,cAAc;CACd,MAAM;CACN,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,eAAe;CACf,UAAU;CACV,eAAe;CACf,OAAO;CACP,aAAa;CACb,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,YAAY;CACZ,eAAe;CACf,SAAS;CACT,eAAe;CACf,WAAW;CACX,OAAO;CACP,OAAO;CACP,WAAW;CACX,aAAa;CACb,MAAM;CACN,UAAU;CACV,MAAM;CACN,SAAS;CACT,aAAa;CACb,cAAc;
|
|
1
|
+
{"version":3,"file":"id.js","names":[],"sources":["../../src/shared/id.ts"],"sourcesContent":["import KSUID from 'ksuid';\n\n/**\n * Prefixed, KSUID-bodied string ids — `${prefix}_${ksuid}`, e.g. `org_2QZ4mK...`.\n *\n * The body is a real KSUID (27-char base62, 4-byte timestamp + 16 random bytes),\n * so ids are lexicographically time-sortable. The prefix marks the entity type at\n * a glance. A core utility (one implementation, nothing to swap), called directly\n * by core services and used as the Drizzle `.$defaultFn` for every domain id column.\n */\nexport const ID_PREFIXES = {\n organization: 'org',\n user: 'usr',\n student: 'stu',\n orgUser: 'orm',\n orgRole: 'orl',\n roleAssignment: 'ras',\n invite: 'ivt',\n bundle: 'bun',\n course: 'crs',\n module: 'mod',\n activity: 'act',\n activityAsset: 'aca',\n download: 'dwn',\n downloadAsset: 'dla',\n contentEvent: 'evn',\n eventAsset: 'eva',\n asset: 'ast',\n entitlement: 'ent',\n progress: 'prg',\n credential: 'crd',\n connection: 'con',\n event: 'evt',\n automation: 'atm',\n automationRun: 'run',\n comment: 'cmt',\n commentReport: 'crp',\n community: 'cmy',\n space: 'spc',\n topic: 'top',\n spaceLink: 'slk',\n spaceMember: 'spm',\n post: 'pst',\n postFlag: 'pfl',\n chat: 'cht',\n message: 'msg',\n messageFlag: 'mfl',\n notification: 'ntf',\n notificationSetting: 'nst',\n notificationDelivery: 'ndl',\n device: 'dev',\n} as const;\n\nexport type IdType = keyof typeof ID_PREFIXES;\n\n/** A bare KSUID body (27-char base62, time-sortable), no prefix. */\nexport function ksuid(): string {\n return KSUID.randomSync().string;\n}\n\n/** Attach an arbitrary prefix to a fresh KSUID — `${prefix}_${ksuid}`. */\nexport function prefixId(prefix: string): string {\n return `${prefix}_${ksuid()}`;\n}\n\n/** Generate a prefixed, time-sortable id for the given domain entity type. */\nexport function genId(type: IdType): string {\n return prefixId(ID_PREFIXES[type]);\n}\n\n/** True when `value` carries the prefix for `type` (shape check only, not existence). */\nexport function isId(type: IdType, value: string): boolean {\n return value.startsWith(`${ID_PREFIXES[type]}_`);\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAa,cAAc;CACzB,cAAc;CACd,MAAM;CACN,SAAS;CACT,SAAS;CACT,SAAS;CACT,gBAAgB;CAChB,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,eAAe;CACf,UAAU;CACV,eAAe;CACf,cAAc;CACd,YAAY;CACZ,OAAO;CACP,aAAa;CACb,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,YAAY;CACZ,eAAe;CACf,SAAS;CACT,eAAe;CACf,WAAW;CACX,OAAO;CACP,OAAO;CACP,WAAW;CACX,aAAa;CACb,MAAM;CACN,UAAU;CACV,MAAM;CACN,SAAS;CACT,aAAa;CACb,cAAc;CACd,qBAAqB;CACrB,sBAAsB;CACtB,QAAQ;AACV;;AAKA,SAAgB,QAAgB;CAC9B,OAAO,MAAM,WAAW,CAAC,CAAC;AAC5B;;AAGA,SAAgB,SAAS,QAAwB;CAC/C,OAAO,GAAG,OAAO,GAAG,MAAM;AAC5B;;AAGA,SAAgB,MAAM,MAAsB;CAC1C,OAAO,SAAS,YAAY,KAAK;AACnC;;AAGA,SAAgB,KAAK,MAAc,OAAwB;CACzD,OAAO,MAAM,WAAW,GAAG,YAAY,MAAM,EAAE;AACjD"}
|
package/dist/shared/logger.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ declare function createCapturingLogger(): {
|
|
|
14
14
|
logger: Logger;
|
|
15
15
|
entries: CapturedLog[];
|
|
16
16
|
};
|
|
17
|
+
declare function describeError(err: unknown): string;
|
|
17
18
|
//#endregion
|
|
18
|
-
export { CapturedLog, createCapturingLogger, noopLogger };
|
|
19
|
+
export { CapturedLog, createCapturingLogger, describeError, noopLogger };
|
|
19
20
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/shared/logger.js
CHANGED
|
@@ -38,7 +38,16 @@ function createCapturingLogger() {
|
|
|
38
38
|
entries
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
function describeError(err) {
|
|
42
|
+
if (err instanceof AggregateError && err.errors.length > 0) return [...new Set(err.errors.map(describeError))].join("; ");
|
|
43
|
+
if (err instanceof Error) {
|
|
44
|
+
const code = err.code;
|
|
45
|
+
const text = err.message || err.name;
|
|
46
|
+
return typeof code === "string" && !text.includes(code) ? `${code}: ${text}` : text;
|
|
47
|
+
}
|
|
48
|
+
return String(err);
|
|
49
|
+
}
|
|
41
50
|
//#endregion
|
|
42
|
-
export { createCapturingLogger, noopLogger };
|
|
51
|
+
export { createCapturingLogger, describeError, noopLogger };
|
|
43
52
|
|
|
44
53
|
//# sourceMappingURL=logger.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","names":[],"sources":["../../src/shared/logger.ts"],"sourcesContent":["// Test/default implementations of the Logger port. The real pino-backed\n// implementation lives in adapters/logging; these exist so constructors can\n// default to a silent logger and tests can assert on emitted entries.\nimport type { Logger } from './ports.js';\n\n/** Discards everything. The default for constructors the container overrides. */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => noopLogger,\n};\n\nexport interface CapturedLog {\n level: 'debug' | 'info' | 'warn' | 'error';\n msg: string;\n meta: Record<string, unknown>;\n}\n\n/** In-memory logger for tests: every entry (from the root or any child) lands\n * in `entries`, with child bindings merged into meta. */\nexport function createCapturingLogger(): { logger: Logger; entries: CapturedLog[] } {\n const entries: CapturedLog[] = [];\n const make = (bindings: Record<string, unknown>): Logger => {\n const record =\n (level: CapturedLog['level']) => (msg: string, meta?: Record<string, unknown>) => {\n entries.push({ level, msg, meta: { ...bindings, ...meta } });\n };\n return {\n debug: record('debug'),\n info: record('info'),\n warn: record('warn'),\n error: record('error'),\n child: (more) => make({ ...bindings, ...more }),\n };\n };\n return { logger: make({}), entries };\n}\n"],"mappings":";;AAMA,MAAa,aAAqB;CAChC,aAAa,CAAC;CACd,YAAY,CAAC;CACb,YAAY,CAAC;CACb,aAAa,CAAC;CACd,aAAa;AACf;;;AAUA,SAAgB,wBAAoE;CAClF,MAAM,UAAyB,CAAC;CAChC,MAAM,QAAQ,aAA8C;EAC1D,MAAM,UACH,WAAiC,KAAa,SAAmC;GAChF,QAAQ,KAAK;IAAE;IAAO;IAAK,MAAM;KAAE,GAAG;KAAU,GAAG;IAAK;GAAE,CAAC;EAC7D;EACF,OAAO;GACL,OAAO,OAAO,OAAO;GACrB,MAAM,OAAO,MAAM;GACnB,MAAM,OAAO,MAAM;GACnB,OAAO,OAAO,OAAO;GACrB,QAAQ,SAAS,KAAK;IAAE,GAAG;IAAU,GAAG;GAAK,CAAC;EAChD;CACF;CACA,OAAO;EAAE,QAAQ,KAAK,CAAC,CAAC;EAAG;CAAQ;AACrC"}
|
|
1
|
+
{"version":3,"file":"logger.js","names":[],"sources":["../../src/shared/logger.ts"],"sourcesContent":["// Test/default implementations of the Logger port. The real pino-backed\n// implementation lives in adapters/logging; these exist so constructors can\n// default to a silent logger and tests can assert on emitted entries.\nimport type { Logger } from './ports.js';\n\n/** Discards everything. The default for constructors the container overrides. */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => noopLogger,\n};\n\nexport interface CapturedLog {\n level: 'debug' | 'info' | 'warn' | 'error';\n msg: string;\n meta: Record<string, unknown>;\n}\n\n/** In-memory logger for tests: every entry (from the root or any child) lands\n * in `entries`, with child bindings merged into meta. */\nexport function createCapturingLogger(): { logger: Logger; entries: CapturedLog[] } {\n const entries: CapturedLog[] = [];\n const make = (bindings: Record<string, unknown>): Logger => {\n const record =\n (level: CapturedLog['level']) => (msg: string, meta?: Record<string, unknown>) => {\n entries.push({ level, msg, meta: { ...bindings, ...meta } });\n };\n return {\n debug: record('debug'),\n info: record('info'),\n warn: record('warn'),\n error: record('error'),\n child: (more) => make({ ...bindings, ...more }),\n };\n };\n return { logger: make({}), entries };\n}\n\nexport function describeError(err: unknown): string {\n if (err instanceof AggregateError && err.errors.length > 0) {\n return [...new Set(err.errors.map(describeError))].join('; ');\n }\n if (err instanceof Error) {\n const code = (err as { code?: unknown }).code;\n const text = err.message || err.name;\n return typeof code === 'string' && !text.includes(code) ? `${code}: ${text}` : text;\n }\n return String(err);\n}\n"],"mappings":";;AAMA,MAAa,aAAqB;CAChC,aAAa,CAAC;CACd,YAAY,CAAC;CACb,YAAY,CAAC;CACb,aAAa,CAAC;CACd,aAAa;AACf;;;AAUA,SAAgB,wBAAoE;CAClF,MAAM,UAAyB,CAAC;CAChC,MAAM,QAAQ,aAA8C;EAC1D,MAAM,UACH,WAAiC,KAAa,SAAmC;GAChF,QAAQ,KAAK;IAAE;IAAO;IAAK,MAAM;KAAE,GAAG;KAAU,GAAG;IAAK;GAAE,CAAC;EAC7D;EACF,OAAO;GACL,OAAO,OAAO,OAAO;GACrB,MAAM,OAAO,MAAM;GACnB,MAAM,OAAO,MAAM;GACnB,OAAO,OAAO,OAAO;GACrB,QAAQ,SAAS,KAAK;IAAE,GAAG;IAAU,GAAG;GAAK,CAAC;EAChD;CACF;CACA,OAAO;EAAE,QAAQ,KAAK,CAAC,CAAC;EAAG;CAAQ;AACrC;AAEA,SAAgB,cAAc,KAAsB;CAClD,IAAI,eAAe,kBAAkB,IAAI,OAAO,SAAS,GACvD,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;CAE9D,IAAI,eAAe,OAAO;EACxB,MAAM,OAAQ,IAA2B;EACzC,MAAM,OAAO,IAAI,WAAW,IAAI;EAChC,OAAO,OAAO,SAAS,YAAY,CAAC,KAAK,SAAS,IAAI,IAAI,GAAG,KAAK,IAAI,SAAS;CACjF;CACA,OAAO,OAAO,GAAG;AACnB"}
|
package/dist/shared/mailer.d.ts
CHANGED
|
@@ -16,6 +16,16 @@ interface MailerLookups {
|
|
|
16
16
|
type: ContentType;
|
|
17
17
|
} | null>;
|
|
18
18
|
}
|
|
19
|
+
declare class EmailRejectedError extends Error {
|
|
20
|
+
readonly status?: number | undefined;
|
|
21
|
+
constructor(message: string, status?: number | undefined);
|
|
22
|
+
}
|
|
23
|
+
declare function isEmailRejected(err: unknown): err is EmailRejectedError;
|
|
24
|
+
declare class EmailRenderError extends Error {
|
|
25
|
+
readonly template: string;
|
|
26
|
+
constructor(template: string, cause: unknown);
|
|
27
|
+
}
|
|
28
|
+
declare function isEmailRenderError(err: unknown): err is EmailRenderError;
|
|
19
29
|
declare class Mailer {
|
|
20
30
|
private readonly templates;
|
|
21
31
|
private readonly email;
|
|
@@ -24,5 +34,5 @@ declare class Mailer {
|
|
|
24
34
|
send<K extends EmailTemplateId>(to: string, id: K, params: EmailTemplateParams[K], ctx?: Partial<TemplateContext>): Promise<void>;
|
|
25
35
|
}
|
|
26
36
|
//#endregion
|
|
27
|
-
export { Mailer, MailerLookups };
|
|
37
|
+
export { EmailRejectedError, EmailRenderError, Mailer, MailerLookups, isEmailRejected, isEmailRenderError };
|
|
28
38
|
//# sourceMappingURL=mailer.d.ts.map
|
package/dist/shared/mailer.js
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
//#region src/shared/mailer.ts
|
|
2
|
+
var EmailRejectedError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(message, status) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.name = "EmailRejectedError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function isEmailRejected(err) {
|
|
11
|
+
return err instanceof Error && err.name === "EmailRejectedError";
|
|
12
|
+
}
|
|
13
|
+
var EmailRenderError = class extends Error {
|
|
14
|
+
template;
|
|
15
|
+
constructor(template, cause) {
|
|
16
|
+
super(`email template ${template} failed to render: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
17
|
+
this.template = template;
|
|
18
|
+
this.name = "EmailRenderError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function isEmailRenderError(err) {
|
|
22
|
+
return err instanceof Error && err.name === "EmailRenderError";
|
|
23
|
+
}
|
|
2
24
|
var Mailer = class {
|
|
3
25
|
templates;
|
|
4
26
|
email;
|
|
@@ -9,10 +31,15 @@ var Mailer = class {
|
|
|
9
31
|
this.ctx = ctx;
|
|
10
32
|
}
|
|
11
33
|
async send(to, id, params, ctx) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
34
|
+
let content;
|
|
35
|
+
try {
|
|
36
|
+
content = await this.templates.render(id, {
|
|
37
|
+
...this.ctx,
|
|
38
|
+
...ctx
|
|
39
|
+
}, params);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
throw new EmailRenderError(id, err);
|
|
42
|
+
}
|
|
16
43
|
await this.email.send({
|
|
17
44
|
to,
|
|
18
45
|
subject: content.subject,
|
|
@@ -22,6 +49,6 @@ var Mailer = class {
|
|
|
22
49
|
}
|
|
23
50
|
};
|
|
24
51
|
//#endregion
|
|
25
|
-
export { Mailer };
|
|
52
|
+
export { EmailRejectedError, EmailRenderError, Mailer, isEmailRejected, isEmailRenderError };
|
|
26
53
|
|
|
27
54
|
//# sourceMappingURL=mailer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mailer.js","names":[],"sources":["../../src/shared/mailer.ts"],"sourcesContent":["// Composes a TemplateRenderer with an EmailSender: callers name a template,\n// the mailer resolves content and hands it to the transport.\nimport type {\n EmailSender,\n EmailTemplateId,\n EmailTemplateParams,\n TemplateContext,\n TemplateRenderer,\n} from './ports.js';\nimport type { ContentType } from '../types/schemas/content.js';\n\n/** Resolves the recipient/content details a template needs when the\n * triggering event carries only row ids. Wired by the app layer. */\nexport interface MailerLookups {\n orgUserPerson(\n orgId: string,\n orgUserId: string,\n ): Promise<{ userId: string; email: string } | null>;\n contentInfo(\n orgId: string,\n contentId: string,\n ): Promise<{ id: string; title: string; type: ContentType } | null>;\n}\n\nexport class Mailer {\n constructor(\n private readonly templates: TemplateRenderer,\n private readonly email: EmailSender,\n private readonly ctx: TemplateContext,\n ) {}\n\n async send<K extends EmailTemplateId>(\n to: string,\n id: K,\n params: EmailTemplateParams[K],\n ctx?: Partial<TemplateContext>,\n ): Promise<void> {\n
|
|
1
|
+
{"version":3,"file":"mailer.js","names":[],"sources":["../../src/shared/mailer.ts"],"sourcesContent":["// Composes a TemplateRenderer with an EmailSender: callers name a template,\n// the mailer resolves content and hands it to the transport.\nimport type {\n EmailSender,\n EmailTemplateId,\n EmailTemplateParams,\n TemplateContext,\n TemplateRenderer,\n} from './ports.js';\nimport type { ContentType } from '../types/schemas/content.js';\n\n/** Resolves the recipient/content details a template needs when the\n * triggering event carries only row ids. Wired by the app layer. */\nexport interface MailerLookups {\n orgUserPerson(\n orgId: string,\n orgUserId: string,\n ): Promise<{ userId: string; email: string } | null>;\n contentInfo(\n orgId: string,\n contentId: string,\n ): Promise<{ id: string; title: string; type: ContentType } | null>;\n}\n\nexport class EmailRejectedError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n ) {\n super(message);\n this.name = 'EmailRejectedError';\n }\n}\n\nexport function isEmailRejected(err: unknown): err is EmailRejectedError {\n return err instanceof Error && err.name === 'EmailRejectedError';\n}\n\nexport class EmailRenderError extends Error {\n constructor(\n readonly template: string,\n cause: unknown,\n ) {\n super(\n `email template ${template} failed to render: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n );\n this.name = 'EmailRenderError';\n }\n}\n\nexport function isEmailRenderError(err: unknown): err is EmailRenderError {\n return err instanceof Error && err.name === 'EmailRenderError';\n}\n\nexport class Mailer {\n constructor(\n private readonly templates: TemplateRenderer,\n private readonly email: EmailSender,\n private readonly ctx: TemplateContext,\n ) {}\n\n async send<K extends EmailTemplateId>(\n to: string,\n id: K,\n params: EmailTemplateParams[K],\n ctx?: Partial<TemplateContext>,\n ): Promise<void> {\n let content: Awaited<ReturnType<TemplateRenderer['render']>>;\n try {\n content = await this.templates.render(id, { ...this.ctx, ...ctx }, params);\n } catch (err) {\n throw new EmailRenderError(id, err);\n }\n await this.email.send({ to, subject: content.subject, text: content.text, html: content.html });\n }\n}\n"],"mappings":";AAwBA,IAAa,qBAAb,cAAwC,MAAM;CAGjC;CAFX,YACE,SACA,QACA;EACA,MAAM,OAAO;EAFJ,KAAA,SAAA;EAGT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,gBAAgB,KAAyC;CACvE,OAAO,eAAe,SAAS,IAAI,SAAS;AAC9C;AAEA,IAAa,mBAAb,cAAsC,MAAM;CAE/B;CADX,YACE,UACA,OACA;EACA,MACE,kBAAkB,SAAS,qBAAqB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KACrG,EAAE,MAAM,CACV;EANS,KAAA,WAAA;EAOT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,mBAAmB,KAAuC;CACxE,OAAO,eAAe,SAAS,IAAI,SAAS;AAC9C;AAEA,IAAa,SAAb,MAAoB;CAEC;CACA;CACA;CAHnB,YACE,WACA,OACA,KACA;EAHiB,KAAA,YAAA;EACA,KAAA,QAAA;EACA,KAAA,MAAA;CAChB;CAEH,MAAM,KACJ,IACA,IACA,QACA,KACe;EACf,IAAI;EACJ,IAAI;GACF,UAAU,MAAM,KAAK,UAAU,OAAO,IAAI;IAAE,GAAG,KAAK;IAAK,GAAG;GAAI,GAAG,MAAM;EAC3E,SAAS,KAAK;GACZ,MAAM,IAAI,iBAAiB,IAAI,GAAG;EACpC;EACA,MAAM,KAAK,MAAM,KAAK;GAAE;GAAI,SAAS,QAAQ;GAAS,MAAM,QAAQ;GAAM,MAAM,QAAQ;EAAK,CAAC;CAChG;AACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NotificationCategory, NotificationTypeDeclaration } from "../types/notifications.js";
|
|
2
|
+
import "../types/index.js";
|
|
3
|
+
import { NotificationTypeRegistry } from "./ports.js";
|
|
4
|
+
import { ValidationProblem } from "./errors.js";
|
|
5
|
+
//#region src/shared/notification-categories.d.ts
|
|
6
|
+
type CategoryListScope = 'org' | 'community';
|
|
7
|
+
declare function coversListScope(declaration: Pick<NotificationTypeDeclaration, 'scopes'>, listScope: CategoryListScope): boolean;
|
|
8
|
+
declare function typesForList(types: Pick<NotificationTypeRegistry, 'list'>, listScope: CategoryListScope): Pick<NotificationTypeDeclaration, 'type' | 'scopes' | 'reasons'>[];
|
|
9
|
+
declare function categoryListProblems(categories: NotificationCategory[], listScope: CategoryListScope, types: Pick<NotificationTypeRegistry, 'get'>, configured: string[]): ValidationProblem[];
|
|
10
|
+
declare function assertValidCategoryList(categories: NotificationCategory[], listScope: CategoryListScope, types: Pick<NotificationTypeRegistry, 'get'>, configured: string[]): void;
|
|
11
|
+
declare function channelOrder(configured: string[]): string[];
|
|
12
|
+
declare function offeringConfigured(categories: NotificationCategory[], configured: string[]): NotificationCategory[];
|
|
13
|
+
declare function storedCategoryList(value: unknown): NotificationCategory[] | null;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { CategoryListScope, assertValidCategoryList, categoryListProblems, channelOrder, coversListScope, offeringConfigured, storedCategoryList, typesForList };
|
|
16
|
+
//# sourceMappingURL=notification-categories.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { notificationCategoryListSchema } from "../types/schemas/notifications.js";
|
|
2
|
+
import { ValidationError } from "./errors.js";
|
|
3
|
+
//#region src/shared/notification-categories.ts
|
|
4
|
+
const LIST_SCOPES = {
|
|
5
|
+
org: ["org"],
|
|
6
|
+
community: [
|
|
7
|
+
"community",
|
|
8
|
+
"space",
|
|
9
|
+
"chat"
|
|
10
|
+
]
|
|
11
|
+
};
|
|
12
|
+
function coversListScope(declaration, listScope) {
|
|
13
|
+
return declaration.scopes.some((scope) => LIST_SCOPES[listScope].includes(scope));
|
|
14
|
+
}
|
|
15
|
+
function typesForList(types, listScope) {
|
|
16
|
+
return types.list().filter((declaration) => coversListScope(declaration, listScope));
|
|
17
|
+
}
|
|
18
|
+
function categoryListProblems(categories, listScope, types, configured) {
|
|
19
|
+
const problems = [];
|
|
20
|
+
const problem = (path, message) => problems.push({
|
|
21
|
+
path,
|
|
22
|
+
message
|
|
23
|
+
});
|
|
24
|
+
const keys = /* @__PURE__ */ new Set();
|
|
25
|
+
const covered = /* @__PURE__ */ new Set();
|
|
26
|
+
categories.forEach((category, index) => {
|
|
27
|
+
const at = `categories[${index}]`;
|
|
28
|
+
if (keys.has(category.key)) problem(`${at}.key`, `the key ${category.key} is used by an earlier category`);
|
|
29
|
+
keys.add(category.key);
|
|
30
|
+
const pairs = [];
|
|
31
|
+
category.covers.forEach((cover, coverIndex) => {
|
|
32
|
+
const coverAt = `${at}.covers[${coverIndex}]`;
|
|
33
|
+
const declaration = types.get(cover.type);
|
|
34
|
+
if (!declaration) {
|
|
35
|
+
problem(`${coverAt}.type`, `${cover.type} is not a notification type`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!coversListScope(declaration, listScope)) problem(`${coverAt}.type`, `${cover.type} cannot be in the ${listScope} list`);
|
|
39
|
+
cover.reasons.forEach((reason, reasonIndex) => {
|
|
40
|
+
if (!declaration.reasons.includes(reason)) problem(`${coverAt}.reasons[${reasonIndex}]`, `${cover.type} does not allow the reason ${reason}`);
|
|
41
|
+
pairs.push(`${cover.type}:${reason}`);
|
|
42
|
+
});
|
|
43
|
+
if (category.perSpace === true && !declaration.scopes.includes("space")) problem(`${coverAt}.type`, `${cover.type} cannot occur in a space, so it cannot be per space`);
|
|
44
|
+
});
|
|
45
|
+
if (Object.keys(category.channels).length === 0) problem(`${at}.channels`, "the category offers no channel");
|
|
46
|
+
for (const channel of Object.keys(category.channels)) if (!configured.includes(channel)) problem(`${at}.channels.${channel}`, `the channel ${channel} is not configured`);
|
|
47
|
+
if (category.perSpace === true && listScope === "org") problem(`${at}.perSpace`, "only a community list can have a per-space category");
|
|
48
|
+
if (category.spaceDefaults !== void 0) {
|
|
49
|
+
if (category.perSpace !== true) problem(`${at}.spaceDefaults`, "spaceDefaults needs perSpace");
|
|
50
|
+
for (const [spaceId, channels] of Object.entries(category.spaceDefaults)) for (const channel of Object.keys(channels)) if (!(channel in category.channels)) problem(`${at}.spaceDefaults.${spaceId}.${channel}`, `spaceDefaults for ${spaceId} names the channel ${channel}, which the category does not offer`);
|
|
51
|
+
}
|
|
52
|
+
if (pairs.length > 0 && pairs.every((pair) => covered.has(pair))) problem(`${at}.covers`, "earlier categories already cover everything it covers");
|
|
53
|
+
for (const pair of pairs) covered.add(pair);
|
|
54
|
+
});
|
|
55
|
+
return problems;
|
|
56
|
+
}
|
|
57
|
+
function assertValidCategoryList(categories, listScope, types, configured) {
|
|
58
|
+
const problems = categoryListProblems(categories, listScope, types, configured);
|
|
59
|
+
if (problems.length > 0) throw new ValidationError(problems);
|
|
60
|
+
}
|
|
61
|
+
const LEADING_CHANNELS = [
|
|
62
|
+
"inApp",
|
|
63
|
+
"email",
|
|
64
|
+
"push"
|
|
65
|
+
];
|
|
66
|
+
function channelOrder(configured) {
|
|
67
|
+
return [...LEADING_CHANNELS.filter((channel) => configured.includes(channel)), ...configured.filter((channel) => !LEADING_CHANNELS.includes(channel))];
|
|
68
|
+
}
|
|
69
|
+
function inOrder(values, order) {
|
|
70
|
+
return Object.fromEntries(order.filter((channel) => channel in values).map((channel) => [channel, values[channel]]));
|
|
71
|
+
}
|
|
72
|
+
function offeringConfigured(categories, configured) {
|
|
73
|
+
const order = channelOrder(configured);
|
|
74
|
+
return categories.flatMap((category) => {
|
|
75
|
+
const channels = inOrder(category.channels, order);
|
|
76
|
+
if (Object.keys(channels).length === 0) return [];
|
|
77
|
+
const { spaceDefaults, ...rest } = category;
|
|
78
|
+
return [{
|
|
79
|
+
...rest,
|
|
80
|
+
channels,
|
|
81
|
+
...spaceDefaults !== void 0 && { spaceDefaults: Object.fromEntries(Object.entries(spaceDefaults).map(([spaceId, values]) => [spaceId, inOrder(values, Object.keys(channels))])) }
|
|
82
|
+
}];
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function storedCategoryList(value) {
|
|
86
|
+
if (value === null || value === void 0) return null;
|
|
87
|
+
const parsed = notificationCategoryListSchema.safeParse(value);
|
|
88
|
+
if (!parsed.success) throw new Error(`stored notification categories are invalid: ${parsed.error.message}`);
|
|
89
|
+
return parsed.data;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { assertValidCategoryList, categoryListProblems, channelOrder, coversListScope, offeringConfigured, storedCategoryList, typesForList };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=notification-categories.js.map
|