@headless-lms/core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/dist/assets/events.d.ts +15 -9
  2. package/dist/assets/file-urls.d.ts +14 -7
  3. package/dist/assets/file-urls.js +15 -4
  4. package/dist/assets/file-urls.js.map +1 -1
  5. package/dist/assets/index.d.ts +4 -4
  6. package/dist/assets/index.js +3 -3
  7. package/dist/assets/ports.d.ts +34 -15
  8. package/dist/assets/service.d.ts +30 -14
  9. package/dist/assets/service.js +233 -54
  10. package/dist/assets/service.js.map +1 -1
  11. package/dist/automations/actions.js.map +1 -1
  12. package/dist/automations/catalog.js +68 -3
  13. package/dist/automations/catalog.js.map +1 -1
  14. package/dist/automations/community-derivations.js +16 -16
  15. package/dist/automations/community-derivations.js.map +1 -1
  16. package/dist/automations/community-mail.d.ts +34 -4
  17. package/dist/automations/community-mail.js +32 -4
  18. package/dist/automations/community-mail.js.map +1 -1
  19. package/dist/automations/event-permissions.d.ts +12 -0
  20. package/dist/automations/event-permissions.js +38 -0
  21. package/dist/automations/event-permissions.js.map +1 -0
  22. package/dist/automations/index.d.ts +2 -1
  23. package/dist/automations/index.js +2 -1
  24. package/dist/automations/notifications/applicable.d.ts +8 -0
  25. package/dist/automations/notifications/applicable.js +12 -0
  26. package/dist/automations/notifications/applicable.js.map +1 -0
  27. package/dist/automations/notifications/debounce.js +1 -1
  28. package/dist/automations/notifications/debounce.js.map +1 -1
  29. package/dist/automations/notifications/defaults.js +33 -3
  30. package/dist/automations/notifications/defaults.js.map +1 -1
  31. package/dist/automations/notifications/processor.js +1 -1
  32. package/dist/automations/notifications/processor.js.map +1 -1
  33. package/dist/automations/notifications/rules/content.js +60 -3
  34. package/dist/automations/notifications/rules/content.js.map +1 -1
  35. package/dist/automations/notifications/rules/discussion.js +29 -29
  36. package/dist/automations/notifications/rules/discussion.js.map +1 -1
  37. package/dist/automations/notifications/rules/entitlements.js +36 -9
  38. package/dist/automations/notifications/rules/entitlements.js.map +1 -1
  39. package/dist/automations/notifications/rules/events.d.ts +9 -0
  40. package/dist/automations/notifications/rules/events.js +27 -0
  41. package/dist/automations/notifications/rules/events.js.map +1 -0
  42. package/dist/automations/notifications/rules/organizations.js +17 -10
  43. package/dist/automations/notifications/rules/organizations.js.map +1 -1
  44. package/dist/automations/notifications/rules/recipients.js +0 -1
  45. package/dist/automations/notifications/rules/recipients.js.map +1 -1
  46. package/dist/automations/notifications/service.d.ts +8 -6
  47. package/dist/automations/notifications/service.js +12 -12
  48. package/dist/automations/notifications/service.js.map +1 -1
  49. package/dist/automations/notifications/types.js +94 -7
  50. package/dist/automations/notifications/types.js.map +1 -1
  51. package/dist/automations/ports.d.ts +30 -31
  52. package/dist/automations/service.d.ts +20 -17
  53. package/dist/automations/service.js +90 -61
  54. package/dist/automations/service.js.map +1 -1
  55. package/dist/content/event-sight.d.ts +11 -0
  56. package/dist/content/event-sight.js +11 -0
  57. package/dist/content/event-sight.js.map +1 -0
  58. package/dist/content/events.d.ts +465 -88
  59. package/dist/content/events.js +34 -1
  60. package/dist/content/events.js.map +1 -1
  61. package/dist/content/index.d.ts +6 -4
  62. package/dist/content/index.js +3 -1
  63. package/dist/content/jobs.d.ts +16 -0
  64. package/dist/content/jobs.js +37 -0
  65. package/dist/content/jobs.js.map +1 -0
  66. package/dist/content/model.d.ts +2 -2
  67. package/dist/content/notification-categories.d.ts +5 -4
  68. package/dist/content/notification-categories.js +15 -6
  69. package/dist/content/notification-categories.js.map +1 -1
  70. package/dist/content/ports.d.ts +113 -68
  71. package/dist/content/service.d.ts +103 -67
  72. package/dist/content/service.js +739 -152
  73. package/dist/content/service.js.map +1 -1
  74. package/dist/content/titles.d.ts +6 -3
  75. package/dist/content/titles.js +37 -24
  76. package/dist/content/titles.js.map +1 -1
  77. package/dist/content/types.d.ts +2 -2
  78. package/dist/discussion/authors.d.ts +4 -2
  79. package/dist/discussion/authors.js +9 -5
  80. package/dist/discussion/authors.js.map +1 -1
  81. package/dist/discussion/chats.d.ts +56 -37
  82. package/dist/discussion/chats.js +392 -160
  83. package/dist/discussion/chats.js.map +1 -1
  84. package/dist/discussion/events.d.ts +85 -47
  85. package/dist/discussion/events.js +41 -3
  86. package/dist/discussion/events.js.map +1 -1
  87. package/dist/discussion/index.d.ts +8 -5
  88. package/dist/discussion/index.js +5 -3
  89. package/dist/discussion/live.d.ts +106 -0
  90. package/dist/discussion/live.js +642 -0
  91. package/dist/discussion/live.js.map +1 -0
  92. package/dist/discussion/mentions.js +2 -2
  93. package/dist/discussion/mentions.js.map +1 -1
  94. package/dist/discussion/ports.d.ts +182 -100
  95. package/dist/discussion/ports.js.map +1 -1
  96. package/dist/discussion/posts.d.ts +45 -39
  97. package/dist/discussion/posts.js +266 -152
  98. package/dist/discussion/posts.js.map +1 -1
  99. package/dist/discussion/rules.d.ts +18 -0
  100. package/dist/discussion/rules.js +22 -0
  101. package/dist/discussion/rules.js.map +1 -0
  102. package/dist/discussion/service.d.ts +37 -31
  103. package/dist/discussion/service.js +250 -186
  104. package/dist/discussion/service.js.map +1 -1
  105. package/dist/discussion/subscriptions.d.ts +8 -8
  106. package/dist/discussion/subscriptions.js +28 -12
  107. package/dist/discussion/subscriptions.js.map +1 -1
  108. package/dist/discussion/titles.d.ts +4 -2
  109. package/dist/discussion/titles.js +27 -13
  110. package/dist/discussion/titles.js.map +1 -1
  111. package/dist/entitlements/events.d.ts +58 -58
  112. package/dist/entitlements/index.d.ts +4 -4
  113. package/dist/entitlements/index.js +2 -2
  114. package/dist/entitlements/model.d.ts +2 -2
  115. package/dist/entitlements/ports.d.ts +65 -39
  116. package/dist/entitlements/rules.d.ts +3 -6
  117. package/dist/entitlements/rules.js +5 -10
  118. package/dist/entitlements/rules.js.map +1 -1
  119. package/dist/entitlements/service.d.ts +40 -28
  120. package/dist/entitlements/service.js +369 -162
  121. package/dist/entitlements/service.js.map +1 -1
  122. package/dist/identity/devices.d.ts +3 -3
  123. package/dist/identity/devices.js +6 -4
  124. package/dist/identity/devices.js.map +1 -1
  125. package/dist/identity/index.d.ts +2 -2
  126. package/dist/identity/index.js +2 -2
  127. package/dist/identity/notification-settings.d.ts +1 -1
  128. package/dist/identity/notification-settings.js +2 -1
  129. package/dist/identity/notification-settings.js.map +1 -1
  130. package/dist/identity/ports.d.ts +7 -7
  131. package/dist/identity/service.d.ts +8 -7
  132. package/dist/identity/service.js +16 -12
  133. package/dist/identity/service.js.map +1 -1
  134. package/dist/integrations/events.d.ts +3 -3
  135. package/dist/integrations/ports.d.ts +9 -8
  136. package/dist/integrations/service.d.ts +9 -8
  137. package/dist/integrations/service.js +31 -14
  138. package/dist/integrations/service.js.map +1 -1
  139. package/dist/organizations/default-roles.d.ts +22 -0
  140. package/dist/organizations/default-roles.js +89 -0
  141. package/dist/organizations/default-roles.js.map +1 -0
  142. package/dist/organizations/effective-permissions.d.ts +47 -0
  143. package/dist/organizations/effective-permissions.js +118 -0
  144. package/dist/organizations/effective-permissions.js.map +1 -0
  145. package/dist/organizations/events.d.ts +326 -181
  146. package/dist/organizations/events.js +90 -18
  147. package/dist/organizations/events.js.map +1 -1
  148. package/dist/organizations/index.d.ts +14 -10
  149. package/dist/organizations/index.js +7 -3
  150. package/dist/organizations/members.d.ts +66 -8
  151. package/dist/organizations/members.js.map +1 -1
  152. package/dist/organizations/model.d.ts +2 -2
  153. package/dist/organizations/notification-catalog.d.ts +31 -0
  154. package/dist/organizations/notification-catalog.js +32 -0
  155. package/dist/organizations/notification-catalog.js.map +1 -0
  156. package/dist/organizations/notification-categories.d.ts +6 -4
  157. package/dist/organizations/notification-categories.js +25 -10
  158. package/dist/organizations/notification-categories.js.map +1 -1
  159. package/dist/organizations/org-roles.d.ts +33 -0
  160. package/dist/organizations/org-roles.js +49 -0
  161. package/dist/organizations/org-roles.js.map +1 -0
  162. package/dist/organizations/permissions.d.ts +311 -0
  163. package/dist/organizations/permissions.js +327 -0
  164. package/dist/organizations/permissions.js.map +1 -0
  165. package/dist/organizations/ports.d.ts +187 -65
  166. package/dist/organizations/service.d.ts +102 -35
  167. package/dist/organizations/service.js +1254 -327
  168. package/dist/organizations/service.js.map +1 -1
  169. package/dist/organizations/testing.d.ts +18 -0
  170. package/dist/organizations/testing.js +37 -0
  171. package/dist/organizations/testing.js.map +1 -0
  172. package/dist/organizations/types.d.ts +5 -16
  173. package/dist/progress/events.d.ts +14 -14
  174. package/dist/progress/index.d.ts +2 -2
  175. package/dist/progress/ports.d.ts +11 -3
  176. package/dist/progress/service.d.ts +6 -5
  177. package/dist/progress/service.js +21 -6
  178. package/dist/progress/service.js.map +1 -1
  179. package/dist/reporting/admin/index.d.ts +6 -4
  180. package/dist/reporting/admin/model.d.ts +36 -3
  181. package/dist/reporting/admin/ports.d.ts +39 -14
  182. package/dist/reporting/admin/service.d.ts +32 -11
  183. package/dist/reporting/admin/service.js +102 -18
  184. package/dist/reporting/admin/service.js.map +1 -1
  185. package/dist/reporting/events/index.d.ts +27 -0
  186. package/dist/reporting/events/index.js +20 -0
  187. package/dist/reporting/events/index.js.map +1 -0
  188. package/dist/reporting/member/calendar.d.ts +11 -0
  189. package/dist/reporting/member/calendar.js +75 -0
  190. package/dist/reporting/member/calendar.js.map +1 -0
  191. package/dist/reporting/member/index.d.ts +3 -2
  192. package/dist/reporting/member/model.d.ts +29 -5
  193. package/dist/reporting/member/ports.d.ts +49 -29
  194. package/dist/reporting/member/service.d.ts +57 -29
  195. package/dist/reporting/member/service.js +345 -114
  196. package/dist/reporting/member/service.js.map +1 -1
  197. package/dist/shared/actor.d.ts +23 -3
  198. package/dist/shared/errors.d.ts +2 -1
  199. package/dist/shared/errors.js +3 -1
  200. package/dist/shared/errors.js.map +1 -1
  201. package/dist/shared/id.d.ts +4 -0
  202. package/dist/shared/id.js +4 -0
  203. package/dist/shared/id.js.map +1 -1
  204. package/dist/shared/logger.d.ts +2 -1
  205. package/dist/shared/logger.js +10 -1
  206. package/dist/shared/logger.js.map +1 -1
  207. package/dist/shared/ports.d.ts +25 -5
  208. package/dist/types/content.d.ts +2 -2
  209. package/dist/types/discussion.d.ts +4 -5
  210. package/dist/types/email-templates.d.ts +22 -3
  211. package/dist/types/entitlements.d.ts +2 -2
  212. package/dist/types/identity.d.ts +13 -16
  213. package/dist/types/index.d.ts +9 -9
  214. package/dist/types/index.js +2 -1
  215. package/dist/types/notifications.d.ts +4 -4
  216. package/dist/types/organizations.d.ts +3 -2
  217. package/dist/types/ports.d.ts +8 -1
  218. package/dist/types/ports.js +7 -1
  219. package/dist/types/ports.js.map +1 -0
  220. package/dist/types/schemas/assets.d.ts +5 -5
  221. package/dist/types/schemas/assets.js +4 -3
  222. package/dist/types/schemas/assets.js.map +1 -1
  223. package/dist/types/schemas/content.d.ts +599 -13
  224. package/dist/types/schemas/content.js +219 -8
  225. package/dist/types/schemas/content.js.map +1 -1
  226. package/dist/types/schemas/discussion.d.ts +280 -57
  227. package/dist/types/schemas/discussion.js +77 -3
  228. package/dist/types/schemas/discussion.js.map +1 -1
  229. package/dist/types/schemas/entitlements.d.ts +21 -8
  230. package/dist/types/schemas/entitlements.js +11 -4
  231. package/dist/types/schemas/entitlements.js.map +1 -1
  232. package/dist/types/schemas/index.d.ts +5 -5
  233. package/dist/types/schemas/index.js +5 -5
  234. package/dist/types/schemas/notifications.d.ts +11 -10
  235. package/dist/types/schemas/notifications.js +3 -2
  236. package/dist/types/schemas/notifications.js.map +1 -1
  237. package/dist/types/schemas/organizations.d.ts +99 -60
  238. package/dist/types/schemas/organizations.js +80 -27
  239. package/dist/types/schemas/organizations.js.map +1 -1
  240. package/dist/types/schemas/progress.d.ts +3 -4
  241. package/dist/types/schemas/progress.js +0 -1
  242. package/dist/types/schemas/progress.js.map +1 -1
  243. package/package.json +1 -1
  244. package/dist/organizations/access-scopes.d.ts +0 -19
  245. package/dist/organizations/access-scopes.js +0 -89
  246. package/dist/organizations/access-scopes.js.map +0 -1
  247. package/dist/organizations/roles.d.ts +0 -19
  248. package/dist/organizations/roles.js +0 -82
  249. package/dist/organizations/roles.js.map +0 -1
@@ -1,7 +1,12 @@
1
- import { isStaffRole } from "../../organizations/roles.js";
2
- import { channelOrder } from "../../shared/notification-categories.js";
1
+ import { PERMISSION_KEYS } from "../../types/schemas/organizations.js";
2
+ import { NotFoundError } from "../../shared/errors.js";
3
3
  import { seesSpace } from "../../entitlements/rules.js";
4
+ import { channelOrder } from "../../shared/notification-categories.js";
5
+ import { eventSight } from "../../content/event-sight.js";
4
6
  import { resolveChannels } from "../../shared/notification-settings.js";
7
+ import { applicableCategories } from "../../automations/notifications/applicable.js";
8
+ import { attendeeFace, attendeeView } from "../events/index.js";
9
+ import { eventCalendar } from "./calendar.js";
5
10
  //#region src/reporting/member/service.ts
6
11
  function refKey(ref) {
7
12
  return `${ref.type}:${ref.id}`;
@@ -22,6 +27,24 @@ function itemView(ref, titles) {
22
27
  function isActivityPublished(settings) {
23
28
  return settings?.published !== false;
24
29
  }
30
+ const READ_OF = {
31
+ course: "courses:read",
32
+ download: "downloads:read",
33
+ community: "communities:read",
34
+ event: "events:read"
35
+ };
36
+ const THUMBNAIL_OF = {
37
+ course: "Course thumbnail",
38
+ download: "Download thumbnail",
39
+ community: "Community thumbnail",
40
+ event: "Event thumbnail"
41
+ };
42
+ const SPACE_IMAGE_ASSET = {
43
+ cover: (space) => space.coverAssetId,
44
+ background: (space) => space.backgroundAssetId,
45
+ icon: (space) => space.iconAssetId
46
+ };
47
+ const EVENT_FACES = 10;
25
48
  var MemberReportServiceImpl = class {
26
49
  repo;
27
50
  content;
@@ -29,12 +52,15 @@ var MemberReportServiceImpl = class {
29
52
  files;
30
53
  fileUrls;
31
54
  access;
55
+ people;
32
56
  notificationList;
33
57
  titles;
34
58
  subscriptions;
35
59
  notificationCategories;
36
60
  notificationSettings;
37
61
  channels;
62
+ studentPortalUrl;
63
+ now;
38
64
  constructor(params) {
39
65
  this.repo = params.repo;
40
66
  this.content = params.content;
@@ -42,35 +68,56 @@ var MemberReportServiceImpl = class {
42
68
  this.files = params.files;
43
69
  this.fileUrls = params.fileUrls;
44
70
  this.access = params.access;
71
+ this.people = params.people;
45
72
  this.notificationList = params.notifications;
46
73
  this.titles = params.titles;
47
74
  this.subscriptions = params.subscriptions;
48
75
  this.notificationCategories = params.notificationCategories;
49
76
  this.notificationSettings = params.notificationSettings;
50
77
  this.channels = params.channels;
78
+ this.studentPortalUrl = params.studentPortalUrl.replace(/\/+$/, "");
79
+ this.now = params.now ?? (() => /* @__PURE__ */ new Date());
51
80
  }
52
- async communityNotificationSettings(orgId, actor, userId, communityId) {
53
- if (!await this.access.isCommunityMember(orgId, actor, communityId)) return null;
81
+ system(actor) {
82
+ return this.people.systemActor(actor.orgId);
83
+ }
84
+ viewer(actor) {
85
+ const role = actor.permissions.roleOn();
86
+ return {
87
+ orgUserId: actor.id,
88
+ orgRole: {
89
+ key: role.key,
90
+ name: role.name
91
+ },
92
+ permissions: PERMISSION_KEYS.filter((p) => actor.permissions.anywhere(p)),
93
+ orgPermissions: PERMISSION_KEYS.filter((p) => actor.permissions.has(p))
94
+ };
95
+ }
96
+ async communityNotificationSettings(communityId, actor) {
97
+ if (!await this.access.isCommunityMember(communityId, actor)) throw new NotFoundError("Community", communityId);
98
+ const orgId = actor.orgId;
99
+ const system = this.system(actor);
54
100
  const [list, settings, spaceIds, spaces, community] = await Promise.all([
55
- this.notificationCategories.community.get(orgId, communityId),
56
- this.notificationSettings.get(orgId, userId, { communityId }),
57
- this.access.openSpaceIds(orgId, actor, communityId),
58
- this.content.listSpaces(orgId, communityId),
59
- this.content.getCommunity(orgId, communityId)
101
+ this.notificationCategories.community.get(communityId, system),
102
+ this.notificationSettings.get(orgId, actor.userId, { communityId }),
103
+ this.access.openSpaceIds(communityId, actor),
104
+ this.content.listSpaces(communityId, system),
105
+ this.content.findCommunity(communityId, system)
60
106
  ]);
61
107
  const open = new Set(spaceIds);
62
108
  return {
63
109
  communityId,
64
110
  digest: community?.settings.digest ?? null,
65
- categories: this.settingsView(list.categories, settings, spaces.filter((space) => open.has(space.id)).map((space) => ({
111
+ categories: this.settingsView(applicableCategories(list.categories, actor.permissions, { communityId }), settings, spaces.filter((space) => open.has(space.id)).map((space) => ({
66
112
  spaceId: space.id,
67
113
  name: space.name
68
114
  })))
69
115
  };
70
116
  }
71
- async orgNotificationSettings(orgId, userId) {
72
- const [list, settings] = await Promise.all([this.notificationCategories.org.get(orgId, orgId), this.notificationSettings.get(orgId, userId, "org")]);
73
- return { categories: this.settingsView(list.categories, settings, []) };
117
+ async orgNotificationSettings(actor) {
118
+ const orgId = actor.orgId;
119
+ const [list, settings] = await Promise.all([this.notificationCategories.org.get(orgId, this.system(actor)), this.notificationSettings.get(orgId, actor.userId, "org")]);
120
+ return { categories: this.settingsView(applicableCategories(list.categories, actor.permissions, "org"), settings, []) };
74
121
  }
75
122
  settingsView(categories, settings, spaces) {
76
123
  const configured = channelOrder(this.channels.list());
@@ -97,27 +144,28 @@ var MemberReportServiceImpl = class {
97
144
  return view;
98
145
  });
99
146
  }
100
- async notifications(orgId, actor, userId, filter, page) {
101
- const listed = await this.notificationList.list(orgId, userId, filter, page);
147
+ async notifications(filter, page, actor) {
148
+ const listed = await this.notificationList.list(filter, page, actor);
102
149
  return {
103
- rows: await this.describeNotifications(orgId, actor, listed.items),
150
+ rows: await this.describeNotifications(actor, listed.items),
104
151
  total: listed.total,
105
152
  page: page.page,
106
153
  pageSize: page.pageSize,
107
154
  unreadCount: listed.unreadCount
108
155
  };
109
156
  }
110
- async notification(orgId, actor, userId, notificationId) {
111
- const found = await this.notificationList.get(orgId, userId, notificationId);
112
- if (!found) return null;
113
- const [view] = await this.describeNotifications(orgId, actor, [found]);
114
- return view ?? null;
157
+ async notification(notificationId, actor) {
158
+ const found = await this.notificationList.get(notificationId, actor);
159
+ const [view] = found ? await this.describeNotifications(actor, [found]) : [];
160
+ if (!view) throw new NotFoundError("Notification", notificationId);
161
+ return view;
115
162
  }
116
- async notificationUnreadCounts(orgId, actor, userId) {
117
- const communityIds = await this.access.openCommunityIds(orgId, actor);
118
- return this.notificationList.unreadCounts(orgId, userId, communityIds);
163
+ async notificationUnreadCounts(actor) {
164
+ const communityIds = await this.access.openCommunityIds(actor);
165
+ return this.notificationList.unreadCounts(communityIds, actor);
119
166
  }
120
- async describeNotifications(orgId, actor, rows) {
167
+ async describeNotifications(actor, rows) {
168
+ const orgId = actor.orgId;
121
169
  const refs = rows.flatMap((row) => [
122
170
  ...row.subject ? [row.subject] : [],
123
171
  ...row.parent ? [row.parent] : [],
@@ -130,11 +178,11 @@ var MemberReportServiceImpl = class {
130
178
  id: row.spaceId
131
179
  }] : []
132
180
  ]);
133
- const actorIds = [...new Set(rows.flatMap((row) => row.actorOrgUserId ?? []))];
181
+ const actorIds = [...new Set(rows.flatMap((row) => row.actorId ?? []))];
134
182
  const [people, discussionTitles, contentTitles] = await Promise.all([
135
183
  this.repo.people(orgId, actorIds),
136
- this.titles.discussion.titlesFor(orgId, actor, refs),
137
- this.titles.content.titlesFor(orgId, actor, refs)
184
+ this.titles.discussion.titlesFor(refs, actor),
185
+ this.titles.content.titlesFor(refs, actor)
138
186
  ]);
139
187
  const titles = {
140
188
  ...contentTitles,
@@ -142,7 +190,7 @@ var MemberReportServiceImpl = class {
142
190
  };
143
191
  const personById = new Map(people.map((person) => [person.orgUserId, person]));
144
192
  return rows.map((row) => {
145
- const person = row.actorOrgUserId ? personById.get(row.actorOrgUserId) : void 0;
193
+ const person = row.actorId ? personById.get(row.actorId) : void 0;
146
194
  const community = row.communityId ? titles[`community:${row.communityId}`] : void 0;
147
195
  const space = row.spaceId ? titles[`space:${row.spaceId}`] : void 0;
148
196
  return {
@@ -164,7 +212,7 @@ var MemberReportServiceImpl = class {
164
212
  actor: person ? {
165
213
  orgUserId: person.orgUserId,
166
214
  name: person.displayName,
167
- imageUrl: person.imageAssetId ? this.fileUrls.personImage(person.orgUserId) : null
215
+ imageUrl: person.imageAssetId ? this.fileUrls.publicImage(orgId, person.orgUserId, person.imageAssetId) : null
168
216
  } : null,
169
217
  subject: itemView(row.subject, titles),
170
218
  parent: itemView(row.parent, titles),
@@ -173,27 +221,44 @@ var MemberReportServiceImpl = class {
173
221
  };
174
222
  });
175
223
  }
176
- async listCourses(orgId, orgUserId) {
177
- const ids = await this.access.listAccessibleContent(orgId, orgUserId, "course");
178
- return (await Promise.all(ids.map((id) => this.content.getCourse(orgId, id)))).filter((c) => c !== null && c.status === "published");
224
+ async opensCourse(courseId, actor) {
225
+ const reads = actor.permissions.has("courses:read", courseId);
226
+ if (!reads && !await this.access.hasAccess(actor.orgId, actor.id, courseId)) return null;
227
+ const course = await this.content.findCourse(courseId, this.system(actor));
228
+ return course && (reads || course.status === "published") ? course : null;
229
+ }
230
+ async opensDownload(downloadId, actor) {
231
+ const reads = actor.permissions.has("downloads:read", downloadId);
232
+ if (!reads && !await this.access.hasAccess(actor.orgId, actor.id, downloadId)) return null;
233
+ const download = await this.content.findDownload(downloadId, this.system(actor));
234
+ return download && (reads || download.status === "published") ? download : null;
179
235
  }
180
- async getCourse(orgId, orgUserId, courseId) {
181
- if (!await this.access.hasAccess(orgId, orgUserId, courseId)) return null;
182
- const course = await this.content.getCourse(orgId, courseId);
183
- return course && course.status === "published" ? course : null;
236
+ async listCourses(actor) {
237
+ const ids = await this.access.listAccessibleContent(actor.orgId, actor.id, "course");
238
+ const system = this.system(actor);
239
+ return (await Promise.all(ids.map((id) => this.content.findCourse(id, system)))).filter((c) => c !== null && c.status === "published");
184
240
  }
185
- async listModules(orgId, orgUserId, courseId) {
186
- if (!await this.getCourse(orgId, orgUserId, courseId)) return null;
187
- return this.content.listCourseModules(orgId, courseId);
241
+ async requireCourse(courseId, actor) {
242
+ const course = await this.opensCourse(courseId, actor);
243
+ if (!course) throw new NotFoundError("Course", courseId);
244
+ return course;
188
245
  }
189
- async listActivities(orgId, orgUserId, courseId) {
190
- if (!await this.getCourse(orgId, orgUserId, courseId)) return null;
191
- return (await this.content.listCourseActivityViews(orgId, courseId)).filter((a) => isActivityPublished(a.settings));
246
+ getCourse(courseId, actor) {
247
+ return this.requireCourse(courseId, actor);
192
248
  }
193
- async courseProgress(orgId, orgUserId, courseId) {
194
- if (!await this.getCourse(orgId, orgUserId, courseId)) return null;
195
- const ids = (await this.content.listCourseActivities(orgId, courseId)).filter((a) => isActivityPublished(a.settings)).map((a) => a.id);
196
- const records = await this.progress.listByTargets(orgId, orgUserId, ids);
249
+ async listModules(courseId, actor) {
250
+ await this.requireCourse(courseId, actor);
251
+ return this.content.listCourseModules(courseId, this.system(actor));
252
+ }
253
+ async listActivities(courseId, actor) {
254
+ await this.requireCourse(courseId, actor);
255
+ return (await this.content.listCourseActivityViews(courseId, this.system(actor))).filter((a) => isActivityPublished(a.settings));
256
+ }
257
+ async courseProgress(courseId, actor) {
258
+ await this.requireCourse(courseId, actor);
259
+ const orgId = actor.orgId;
260
+ const ids = (await this.content.listCourseActivities(courseId, this.system(actor))).filter((a) => isActivityPublished(a.settings)).map((a) => a.id);
261
+ const records = await this.progress.listByTargets(orgId, actor.id, ids);
197
262
  const activities = {};
198
263
  const positions = {};
199
264
  let done = 0;
@@ -204,7 +269,7 @@ var MemberReportServiceImpl = class {
204
269
  if (r.position != null) positions[r.targetId] = r.position;
205
270
  }
206
271
  const courseRecord = await this.progress.get(orgId, {
207
- orgUserId,
272
+ orgUserId: actor.id,
208
273
  targetType: "course",
209
274
  targetId: courseId
210
275
  });
@@ -215,86 +280,244 @@ var MemberReportServiceImpl = class {
215
280
  positions
216
281
  };
217
282
  }
218
- async listDownloads(orgId, orgUserId) {
219
- const ids = await this.access.listAccessibleContent(orgId, orgUserId, "download");
220
- return (await Promise.all(ids.map((id) => this.content.getDownload(orgId, id)))).filter((d) => d !== null && d.status === "published");
283
+ async listDownloads(actor) {
284
+ const ids = await this.access.listAccessibleContent(actor.orgId, actor.id, "download");
285
+ const system = this.system(actor);
286
+ return (await Promise.all(ids.map((id) => this.content.findDownload(id, system)))).filter((d) => d !== null && d.status === "published");
221
287
  }
222
- async getDownload(orgId, orgUserId, downloadId) {
223
- if (!await this.access.hasAccess(orgId, orgUserId, downloadId)) return null;
224
- const download = await this.content.getDownload(orgId, downloadId);
225
- if (!download || download.status !== "published") return null;
288
+ async getDownload(downloadId, actor) {
289
+ const download = await this.opensDownload(downloadId, actor);
290
+ if (!download) throw new NotFoundError("Download", downloadId);
226
291
  return {
227
292
  download,
228
- assets: await this.content.listDownloadAssets(orgId, downloadId)
293
+ assets: await this.content.listDownloadAssets(downloadId, this.system(actor))
229
294
  };
230
295
  }
231
- async thumbnailFile(orgId, actor, owner, id) {
232
- const item = owner === "course" ? await this.content.getCourse(orgId, id) : owner === "download" ? await this.content.getDownload(orgId, id) : await this.content.getCommunity(orgId, id);
233
- if (!item?.thumbnailAssetId || item.status !== "published" && !isStaffRole(actor.role)) return null;
234
- return this.files.signFile(orgId, item.thumbnailAssetId);
296
+ async thumbnailFile(owner, id, actor) {
297
+ const system = this.system(actor);
298
+ const item = owner === "event" ? (await this.seeEvents([id], actor))[0]?.event : owner === "course" ? await this.content.findCourse(id, system) : owner === "download" ? await this.content.findDownload(id, system) : await this.content.findCommunity(id, system);
299
+ if (!item?.thumbnailAssetId || owner !== "event" && item.status !== "published" && !actor.permissions.has(READ_OF[owner], id)) throw new NotFoundError(THUMBNAIL_OF[owner], id);
300
+ return signed(await this.files.signFile(actor.orgId, item.thumbnailAssetId), THUMBNAIL_OF[owner], id);
301
+ }
302
+ async spaceImageFile(spaceId, image, actor) {
303
+ const system = this.system(actor);
304
+ const space = await this.content.getSpace(spaceId, system);
305
+ const assetId = space ? SPACE_IMAGE_ASSET[image](space) : null;
306
+ const community = space ? await this.content.findCommunity(space.communityId, system) : null;
307
+ if (!space || !assetId || !community || community.status !== "published" && !actor.permissions.has("communities:read", space.communityId)) throw new NotFoundError("Space image", spaceId);
308
+ return signed(await this.files.signFile(actor.orgId, assetId), "Space image", spaceId);
235
309
  }
236
- async activityFile(orgId, actor, activityId, assetId) {
237
- const activity = await this.content.getActivity(orgId, activityId);
238
- if (!activity) return null;
239
- if (!isStaffRole(actor.role)) {
240
- if (!isActivityPublished(activity.settings) || !await this.getCourse(orgId, actor.id, activity.courseId)) return null;
310
+ async activityFile(activityId, assetId, actor) {
311
+ const system = this.system(actor);
312
+ const activity = await this.content.getActivity(activityId, system);
313
+ if (!activity) throw new NotFoundError("Lesson file", assetId);
314
+ if (!actor.permissions.has("courses:read", activity.courseId)) {
315
+ if (!isActivityPublished(activity.settings) || !await this.opensCourse(activity.courseId, actor)) throw new NotFoundError("Lesson file", assetId);
241
316
  }
242
- if (!(await this.content.listCourseActivityAssets(orgId, activity.courseId)).some((link) => link.activityId === activityId && link.assetId === assetId)) return null;
243
- return this.files.signFile(orgId, assetId);
244
- }
245
- async downloadFile(orgId, actor, downloadId, assetId) {
246
- if (!isStaffRole(actor.role) && !await this.getDownload(orgId, actor.id, downloadId)) return null;
247
- const file = (await this.content.listDownloadAssets(orgId, downloadId)).find((f) => f.assetId === assetId);
248
- if (!file) return null;
249
- return this.files.signFile(orgId, assetId, { saveAs: file.displayName ?? file.filename });
250
- }
251
- async personImage(orgId, orgUserId) {
252
- const [person] = await this.repo.people(orgId, [orgUserId]);
253
- if (!person?.imageAssetId) return null;
254
- return this.files.signFile(orgId, person.imageAssetId);
255
- }
256
- async directory(orgId, actor, communityId, query) {
257
- if (!await this.access.isCommunityMember(orgId, actor, communityId)) return null;
258
- if (!await this.content.getCommunity(orgId, communityId)) return null;
259
- const page = await this.repo.directory(orgId, communityId, query);
317
+ if (!(await this.content.listCourseActivityAssets(activity.courseId, system)).some((link) => link.activityId === activityId && link.assetId === assetId)) throw new NotFoundError("Lesson file", assetId);
318
+ return signed(await this.files.signFile(actor.orgId, assetId), "Lesson file", assetId);
319
+ }
320
+ async downloadFile(downloadId, assetId, actor) {
321
+ if (!await this.opensDownload(downloadId, actor)) throw new NotFoundError("Download file", assetId);
322
+ const file = (await this.content.listDownloadAssets(downloadId, this.system(actor))).find((f) => f.assetId === assetId);
323
+ if (!file) throw new NotFoundError("Download file", assetId);
324
+ return signed(await this.files.signFile(actor.orgId, assetId, { saveAs: file.displayName ?? file.filename }), "Download file", assetId);
325
+ }
326
+ async seeEvents(ids, actor) {
327
+ if (ids.length === 0) return [];
328
+ const system = this.system(actor);
329
+ const [events, attended] = await Promise.all([this.content.findEvents(ids, system), ids.length === 1 ? this.access.hasAccess(actor.orgId, actor.id, ids[0]).then((attending) => attending ? [ids[0]] : []) : this.access.listAccessibleContent(actor.orgId, actor.id, "event")]);
330
+ const attending = new Set(attended);
331
+ const byId = new Map(events.map((event) => [event.id, event]));
332
+ return ids.flatMap((id) => {
333
+ const event = byId.get(id);
334
+ if (!event) return [];
335
+ const sight = eventSight(event, {
336
+ canRead: actor.permissions.has("events:read", id),
337
+ attending: attending.has(id)
338
+ });
339
+ return sight ? [{
340
+ event,
341
+ sight,
342
+ attending: attending.has(id)
343
+ }] : [];
344
+ });
345
+ }
346
+ async requireEvent(eventId, actor) {
347
+ const [seen] = await this.seeEvents([eventId], actor);
348
+ if (!seen) throw new NotFoundError("Event", eventId);
349
+ return seen;
350
+ }
351
+ async eventSummaries(seen, actor, now) {
352
+ const ids = seen.map((item) => item.event.id);
353
+ const attended = seen.filter((item) => item.attending).map((item) => item.event.id);
354
+ const [faces, direct] = await Promise.all([this.repo.eventFaces(actor.orgId, ids, EVENT_FACES), attended.length > 0 ? this.repo.directEventGrants(actor.orgId, actor.id, attended) : Promise.resolve([])]);
355
+ const leaves = new Set(direct);
356
+ return seen.map((item) => this.eventSummary(item, faces[item.event.id] ?? {
357
+ total: 0,
358
+ rows: []
359
+ }, leaves.has(item.event.id), actor, now));
360
+ }
361
+ eventSummary(seen, faces, canLeave, actor, now) {
362
+ const { event, sight, attending } = seen;
363
+ const id = event.id;
364
+ const canOpen = sight !== "public";
365
+ const capacity = event.settings.capacity;
366
+ const attendeeCount = faces.total;
367
+ const ended = event.endsAt.getTime() <= now.getTime();
368
+ const recording = sight === "read" || sight === "attendee" && ended && event.settings.replay ? event.recording : null;
369
+ const writes = actor.permissions.has("events:write", id);
370
+ const canManageAttendees = actor.permissions.has("access:write") || writes;
371
+ return {
372
+ orgId: event.orgId,
373
+ id,
374
+ type: event.type,
375
+ title: event.title,
376
+ thumbnailAssetId: event.thumbnailAssetId,
377
+ thumbnailUrl: event.thumbnailUrl,
378
+ status: event.status,
379
+ startsAt: event.startsAt,
380
+ endsAt: event.endsAt,
381
+ timeZone: event.timeZone,
382
+ location: canOpen ? event.location : null,
383
+ recording,
384
+ settings: {
385
+ type: event.settings.type,
386
+ capacity,
387
+ replay: event.settings.replay
388
+ },
389
+ attendeeCount,
390
+ attendees: faces.rows.map((row) => attendeeFace(actor.orgId, row, this.fileUrls)),
391
+ placesLeft: capacity === null ? null : Math.max(capacity - attendeeCount, 0),
392
+ viewer: {
393
+ attending,
394
+ canRegister: event.status === "published" && !attending && event.settings.type === "public" && event.startsAt.getTime() > now.getTime() && (capacity === null || attendeeCount < capacity),
395
+ canOpen,
396
+ canLeave,
397
+ canManageAttendees,
398
+ canAddAttendees: canManageAttendees && event.status === "published" && !ended,
399
+ canEdit: writes,
400
+ canCancel: writes,
401
+ canDelete: actor.permissions.has("events:delete", id)
402
+ }
403
+ };
404
+ }
405
+ async getEvent(eventId, actor) {
406
+ const seen = await this.requireEvent(eventId, actor);
407
+ const now = this.now();
408
+ const [[summary], files] = await Promise.all([this.eventSummaries([seen], actor, now), seen.sight === "public" ? Promise.resolve({}) : this.content.eventFilesFor([eventId], this.system(actor))]);
409
+ return {
410
+ ...summary,
411
+ description: seen.event.description,
412
+ files: files[eventId] ?? []
413
+ };
414
+ }
415
+ async listEvents(query, actor) {
416
+ const now = this.now();
417
+ const reads = actor.permissions.where("events:read");
418
+ const page = await this.repo.events(actor.orgId, query, {
419
+ orgUserId: actor.id,
420
+ readAll: reads.org,
421
+ readIds: reads.content
422
+ }, now);
423
+ const rows = await this.eventSummaries(await this.seeEvents(page.rows, actor), actor, now);
424
+ return {
425
+ ...page,
426
+ rows
427
+ };
428
+ }
429
+ async eventCalendar(eventId, actor) {
430
+ const { event, sight } = await this.requireEvent(eventId, actor);
431
+ return eventCalendar(event, {
432
+ pageUrl: `${this.studentPortalUrl}/events/${encodeURIComponent(event.id)}`,
433
+ withLocation: sight !== "public",
434
+ now: this.now()
435
+ });
436
+ }
437
+ async eventAttendees(eventId, query, actor) {
438
+ actor.permissions.require("events:read", eventId);
439
+ if (!await this.content.findEvent(eventId, this.system(actor))) throw new NotFoundError("Event", eventId);
440
+ const page = await this.repo.eventAttendees(actor.orgId, eventId, query);
260
441
  return {
261
442
  ...page,
262
- rows: page.rows.map((row) => ({
263
- ...row,
264
- imageUrl: row.imageAssetId ? this.fileUrls.personImage(row.orgUserId) : null
265
- }))
443
+ rows: page.rows.map((row) => attendeeView(actor.orgId, row, this.fileUrls))
266
444
  };
267
445
  }
268
- async communityHome(orgId, actor, communityId) {
269
- if (!await this.access.isCommunityMember(orgId, actor, communityId)) return null;
270
- const community = await this.content.getCommunity(orgId, communityId);
271
- if (!community) return null;
272
- const spaces = await this.content.listSpaces(orgId, communityId);
446
+ async eventFile(eventId, assetId, actor) {
447
+ const [seen] = await this.seeEvents([eventId], actor);
448
+ if (!seen || seen.sight === "public") throw new NotFoundError("Event file", assetId);
449
+ const file = (await this.content.eventFilesFor([eventId], this.system(actor)))[eventId]?.find((f) => f.assetId === assetId);
450
+ if (!file) throw new NotFoundError("Event file", assetId);
451
+ return signed(await this.files.signFile(actor.orgId, assetId, { saveAs: file.displayName ?? file.filename }), "Event file", assetId);
452
+ }
453
+ async directory(communityId, query, actor) {
454
+ if (!await this.access.isCommunityMember(communityId, actor)) throw new NotFoundError("Community", communityId);
455
+ const orgId = actor.orgId;
456
+ if (!await this.content.findCommunity(communityId, this.system(actor))) throw new NotFoundError("Community", communityId);
457
+ const readers = await this.people.holders(orgId, "communities:read", communityId);
458
+ const page = await this.repo.directory(orgId, communityId, query, readers);
459
+ const permissions = await this.people.permissionsOfMany(orgId, page.rows.map((row) => row.orgUserId));
460
+ return {
461
+ ...page,
462
+ rows: page.rows.map((row) => {
463
+ const held = permissions.get(row.orgUserId);
464
+ const item = held?.roleOn(communityId);
465
+ const org = held?.roleOn();
466
+ return {
467
+ ...row,
468
+ imageUrl: row.imageAssetId ? this.fileUrls.publicImage(orgId, row.orgUserId, row.imageAssetId) : null,
469
+ team: (item?.team ?? false) || (org?.team ?? false),
470
+ roleName: item?.team ? item.name : org?.team ? org.name : null,
471
+ opensEverySpace: (held?.has("communities:read", communityId) || held?.has("communities:moderate", communityId)) ?? false
472
+ };
473
+ })
474
+ };
475
+ }
476
+ async communityHome(communityId, actor) {
477
+ if (!await this.access.isCommunityMember(communityId, actor)) throw new NotFoundError("Community", communityId);
478
+ const system = this.system(actor);
479
+ const community = await this.content.findCommunity(communityId, system);
480
+ if (!community) throw new NotFoundError("Community", communityId);
481
+ const [spaces, everySpace] = await Promise.all([this.content.listSpaces(communityId, system), this.access.opensEverySpace(actor, communityId)]);
482
+ const itemRole = actor.permissions.roleOn(communityId);
483
+ const orgRole = actor.permissions.roleOn();
484
+ const role = itemRole.team || !orgRole.team ? itemRole : orgRole;
273
485
  return {
274
486
  communityId,
275
487
  title: community.title,
276
488
  description: community.description,
277
489
  thumbnailUrl: community.thumbnailUrl,
278
490
  dm: community.settings.dm,
279
- spaces: await this.homeSpaces(orgId, actor, communityId, spaces)
491
+ spaces: await this.homeSpaces(actor, communityId, spaces, everySpace),
492
+ viewer: {
493
+ permissions: PERMISSION_KEYS.filter((p) => actor.permissions.has(p, communityId)),
494
+ opensEverySpace: everySpace,
495
+ team: itemRole.team || orgRole.team,
496
+ roleName: role.name,
497
+ canCreateEvents: actor.permissions.has("events:write")
498
+ }
280
499
  };
281
500
  }
282
- async communitySpace(orgId, actor, communityId, spaceId) {
283
- if (!await this.access.isCommunityMember(orgId, actor, communityId)) return null;
284
- const space = await this.content.getSpace(orgId, spaceId);
285
- if (!space || space.communityId !== communityId) return null;
286
- const [view] = await this.homeSpaces(orgId, actor, communityId, [space]);
287
- return view ?? null;
501
+ async communitySpace(communityId, spaceId, actor) {
502
+ if (!await this.access.isCommunityMember(communityId, actor)) throw new NotFoundError("Space", spaceId);
503
+ const space = await this.content.getSpace(spaceId, this.system(actor));
504
+ if (!space || space.communityId !== communityId) throw new NotFoundError("Space", spaceId);
505
+ const everySpace = await this.access.opensEverySpace(actor, communityId);
506
+ const [view] = await this.homeSpaces(actor, communityId, [space], everySpace);
507
+ if (!view) throw new NotFoundError("Space", spaceId);
508
+ return view;
288
509
  }
289
- async homeSpaces(orgId, actor, communityId, spaces) {
510
+ async homeSpaces(actor, communityId, spaces, everySpace) {
511
+ const orgId = actor.orgId;
512
+ const excluding = await this.access.everySpaceOpeners(orgId, communityId);
290
513
  const [openIds, counts, standings, subscribed] = await Promise.all([
291
- this.access.openSpaceIds(orgId, actor, communityId),
292
- this.repo.spaceMemberCounts(orgId, communityId),
293
- this.access.spaceStandings(orgId, actor, communityId),
514
+ this.access.openSpaceIds(communityId, actor),
515
+ this.repo.spaceMemberCounts(orgId, communityId, excluding),
516
+ this.access.spaceStandings(communityId, actor),
294
517
  this.subscriptions.viewerSubscriptions(orgId, actor.id, "space", spaces.map((space) => space.id), /* @__PURE__ */ new Date())
295
518
  ]);
296
519
  const open = new Set(openIds);
297
- const seen = spaces.filter((space) => seesSpace(actor.role, space.accessRule, standings[space.id]));
520
+ const seen = spaces.filter((space) => seesSpace(everySpace, space.accessRule, standings[space.id]));
298
521
  return Promise.all(seen.map(async (space) => {
299
522
  const memberCount = counts[space.id] ?? 0;
300
523
  const base = {
@@ -302,7 +525,10 @@ var MemberReportServiceImpl = class {
302
525
  name: space.name,
303
526
  memberCount,
304
527
  accessRule: space.accessRule,
305
- standing: standingOf(space, open.has(space.id), memberCount, standings[space.id])
528
+ standing: standingOf(space, open.has(space.id), memberCount, standings[space.id]),
529
+ coverUrl: space.coverUrl,
530
+ backgroundUrl: space.backgroundUrl,
531
+ iconUrl: space.iconUrl
306
532
  };
307
533
  if (!open.has(space.id)) return {
308
534
  ...base,
@@ -318,30 +544,31 @@ var MemberReportServiceImpl = class {
318
544
  id: topic.id,
319
545
  name: topic.name
320
546
  })),
321
- links: await this.linkViews(orgId, actor, space.links),
547
+ links: await this.linkViews(actor, space.links),
322
548
  subscription: subscribed[space.id] ?? null
323
549
  };
324
550
  }));
325
551
  }
326
- async linkViews(orgId, actor, links) {
327
- const shown = (status) => status === "published" || isStaffRole(actor.role);
552
+ async linkViews(actor, links) {
553
+ const system = this.system(actor);
554
+ const shown = (type, id, status) => status === "published" || actor.permissions.has(READ_OF[type], id);
328
555
  return (await Promise.all(links.map(async (link) => {
329
- const course = await this.content.getCourse(orgId, link.contentId);
330
- if (course) return shown(course.status) ? {
556
+ const course = await this.content.findCourse(link.contentId, system);
557
+ if (course) return shown("course", link.contentId, course.status) ? {
331
558
  id: link.id,
332
559
  contentId: link.contentId,
333
560
  type: "course",
334
561
  title: course.title
335
562
  } : null;
336
- const download = await this.content.getDownload(orgId, link.contentId);
337
- if (download) return shown(download.status) ? {
563
+ const download = await this.content.findDownload(link.contentId, system);
564
+ if (download) return shown("download", link.contentId, download.status) ? {
338
565
  id: link.id,
339
566
  contentId: link.contentId,
340
567
  type: "download",
341
568
  title: download.title
342
569
  } : null;
343
- const community = await this.content.getCommunity(orgId, link.contentId);
344
- if (community) return shown(community.status) ? {
570
+ const community = await this.content.findCommunity(link.contentId, system);
571
+ if (community) return shown("community", link.contentId, community.status) ? {
345
572
  id: link.id,
346
573
  contentId: link.contentId,
347
574
  type: "community",
@@ -351,6 +578,10 @@ var MemberReportServiceImpl = class {
351
578
  }))).filter((view) => view !== null);
352
579
  }
353
580
  };
581
+ function signed(url, what, id) {
582
+ if (!url) throw new NotFoundError(what, id);
583
+ return url;
584
+ }
354
585
  function standingOf(space, open, memberCount, status) {
355
586
  if (space.accessRule === "all_members" || open && status !== "active") return "open";
356
587
  if (status === "active") return "member";