@headless-lms/core 0.4.0 → 0.5.0

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