@headless-lms/core 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/dist/assets/events.d.ts +24 -18
  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 +5 -4
  6. package/dist/assets/index.js +4 -3
  7. package/dist/assets/jobs.d.ts +15 -0
  8. package/dist/assets/jobs.js +30 -0
  9. package/dist/assets/jobs.js.map +1 -0
  10. package/dist/assets/ports.d.ts +37 -17
  11. package/dist/assets/service.d.ts +30 -15
  12. package/dist/assets/service.js +229 -59
  13. package/dist/assets/service.js.map +1 -1
  14. package/dist/automations/actions.js +2 -63
  15. package/dist/automations/actions.js.map +1 -1
  16. package/dist/automations/catalog.d.ts +1 -2
  17. package/dist/automations/catalog.js +137 -65
  18. package/dist/automations/catalog.js.map +1 -1
  19. package/dist/automations/community-derivations.js +20 -20
  20. package/dist/automations/community-derivations.js.map +1 -1
  21. package/dist/automations/community-mail.d.ts +36 -20
  22. package/dist/automations/community-mail.js +32 -24
  23. package/dist/automations/community-mail.js.map +1 -1
  24. package/dist/automations/event-permissions.d.ts +12 -0
  25. package/dist/automations/event-permissions.js +38 -0
  26. package/dist/automations/event-permissions.js.map +1 -0
  27. package/dist/automations/events.d.ts +0 -10
  28. package/dist/automations/index.d.ts +12 -4
  29. package/dist/automations/index.js +9 -1
  30. package/dist/automations/notifications/applicable.d.ts +8 -0
  31. package/dist/automations/notifications/applicable.js +12 -0
  32. package/dist/automations/notifications/applicable.js.map +1 -0
  33. package/dist/automations/notifications/debounce.d.ts +25 -0
  34. package/dist/automations/notifications/debounce.js +93 -0
  35. package/dist/automations/notifications/debounce.js.map +1 -0
  36. package/dist/automations/notifications/defaults.d.ts +8 -0
  37. package/dist/automations/notifications/defaults.js +346 -0
  38. package/dist/automations/notifications/defaults.js.map +1 -0
  39. package/dist/automations/notifications/delivery.d.ts +28 -0
  40. package/dist/automations/notifications/delivery.js +88 -0
  41. package/dist/automations/notifications/delivery.js.map +1 -0
  42. package/dist/automations/notifications/processor.d.ts +64 -0
  43. package/dist/automations/notifications/processor.js +270 -0
  44. package/dist/automations/notifications/processor.js.map +1 -0
  45. package/dist/automations/notifications/rules/content.d.ts +6 -0
  46. package/dist/automations/notifications/rules/content.js +96 -0
  47. package/dist/automations/notifications/rules/content.js.map +1 -0
  48. package/dist/automations/notifications/rules/discussion.d.ts +7 -0
  49. package/dist/automations/notifications/rules/discussion.js +678 -0
  50. package/dist/automations/notifications/rules/discussion.js.map +1 -0
  51. package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
  52. package/dist/automations/notifications/rules/entitlements.js +276 -0
  53. package/dist/automations/notifications/rules/entitlements.js.map +1 -0
  54. package/dist/automations/notifications/rules/events.d.ts +9 -0
  55. package/dist/automations/notifications/rules/events.js +27 -0
  56. package/dist/automations/notifications/rules/events.js.map +1 -0
  57. package/dist/automations/notifications/rules/index.d.ts +6 -0
  58. package/dist/automations/notifications/rules/index.js +15 -0
  59. package/dist/automations/notifications/rules/index.js.map +1 -0
  60. package/dist/automations/notifications/rules/organizations.d.ts +6 -0
  61. package/dist/automations/notifications/rules/organizations.js +35 -0
  62. package/dist/automations/notifications/rules/organizations.js.map +1 -0
  63. package/dist/automations/notifications/rules/recipients.d.ts +32 -0
  64. package/dist/automations/notifications/rules/recipients.js +98 -0
  65. package/dist/automations/notifications/rules/recipients.js.map +1 -0
  66. package/dist/automations/notifications/service.d.ts +29 -0
  67. package/dist/automations/notifications/service.js +45 -0
  68. package/dist/automations/notifications/service.js.map +1 -0
  69. package/dist/automations/notifications/types.d.ts +7 -0
  70. package/dist/automations/notifications/types.js +560 -0
  71. package/dist/automations/notifications/types.js.map +1 -0
  72. package/dist/automations/ports.d.ts +134 -21
  73. package/dist/automations/service.d.ts +30 -30
  74. package/dist/automations/service.js +111 -144
  75. package/dist/automations/service.js.map +1 -1
  76. package/dist/content/event-sight.d.ts +11 -0
  77. package/dist/content/event-sight.js +11 -0
  78. package/dist/content/event-sight.js.map +1 -0
  79. package/dist/content/events.d.ts +437 -46
  80. package/dist/content/events.js +45 -1
  81. package/dist/content/events.js.map +1 -1
  82. package/dist/content/index.d.ts +8 -4
  83. package/dist/content/index.js +5 -1
  84. package/dist/content/jobs.d.ts +16 -0
  85. package/dist/content/jobs.js +37 -0
  86. package/dist/content/jobs.js.map +1 -0
  87. package/dist/content/model.d.ts +2 -2
  88. package/dist/content/notification-categories.d.ts +37 -0
  89. package/dist/content/notification-categories.js +73 -0
  90. package/dist/content/notification-categories.js.map +1 -0
  91. package/dist/content/ports.d.ts +132 -68
  92. package/dist/content/service.d.ts +100 -68
  93. package/dist/content/service.js +703 -146
  94. package/dist/content/service.js.map +1 -1
  95. package/dist/content/titles.d.ts +25 -0
  96. package/dist/content/titles.js +82 -0
  97. package/dist/content/titles.js.map +1 -0
  98. package/dist/content/types.d.ts +2 -2
  99. package/dist/discussion/authors.d.ts +4 -2
  100. package/dist/discussion/authors.js +9 -5
  101. package/dist/discussion/authors.js.map +1 -1
  102. package/dist/discussion/chats.d.ts +63 -39
  103. package/dist/discussion/chats.js +455 -190
  104. package/dist/discussion/chats.js.map +1 -1
  105. package/dist/discussion/digests.d.ts +24 -0
  106. package/dist/discussion/digests.js +104 -0
  107. package/dist/discussion/digests.js.map +1 -0
  108. package/dist/discussion/events.d.ts +227 -23
  109. package/dist/discussion/events.js +114 -19
  110. package/dist/discussion/events.js.map +1 -1
  111. package/dist/discussion/index.d.ts +12 -4
  112. package/dist/discussion/index.js +9 -2
  113. package/dist/discussion/jobs.d.ts +16 -0
  114. package/dist/discussion/jobs.js +40 -0
  115. package/dist/discussion/jobs.js.map +1 -0
  116. package/dist/discussion/live.d.ts +106 -0
  117. package/dist/discussion/live.js +642 -0
  118. package/dist/discussion/live.js.map +1 -0
  119. package/dist/discussion/mentions.d.ts +11 -0
  120. package/dist/discussion/mentions.js +35 -0
  121. package/dist/discussion/mentions.js.map +1 -0
  122. package/dist/discussion/ports.d.ts +279 -114
  123. package/dist/discussion/ports.js.map +1 -1
  124. package/dist/discussion/posts.d.ts +60 -44
  125. package/dist/discussion/posts.js +453 -287
  126. package/dist/discussion/posts.js.map +1 -1
  127. package/dist/discussion/rules.d.ts +18 -0
  128. package/dist/discussion/rules.js +22 -0
  129. package/dist/discussion/rules.js.map +1 -0
  130. package/dist/discussion/service.d.ts +50 -35
  131. package/dist/discussion/service.js +333 -205
  132. package/dist/discussion/service.js.map +1 -1
  133. package/dist/discussion/subscriptions.d.ts +37 -0
  134. package/dist/discussion/subscriptions.js +115 -0
  135. package/dist/discussion/subscriptions.js.map +1 -0
  136. package/dist/discussion/titles.d.ts +28 -0
  137. package/dist/discussion/titles.js +98 -0
  138. package/dist/discussion/titles.js.map +1 -0
  139. package/dist/entitlements/events.d.ts +20 -20
  140. package/dist/entitlements/index.d.ts +5 -4
  141. package/dist/entitlements/index.js +3 -2
  142. package/dist/entitlements/jobs.d.ts +12 -0
  143. package/dist/entitlements/jobs.js +18 -0
  144. package/dist/entitlements/jobs.js.map +1 -0
  145. package/dist/entitlements/model.d.ts +2 -2
  146. package/dist/entitlements/ports.d.ts +68 -38
  147. package/dist/entitlements/rules.d.ts +3 -6
  148. package/dist/entitlements/rules.js +5 -10
  149. package/dist/entitlements/rules.js.map +1 -1
  150. package/dist/entitlements/service.d.ts +43 -28
  151. package/dist/entitlements/service.js +387 -160
  152. package/dist/entitlements/service.js.map +1 -1
  153. package/dist/identity/devices.d.ts +30 -0
  154. package/dist/identity/devices.js +109 -0
  155. package/dist/identity/devices.js.map +1 -0
  156. package/dist/identity/index.d.ts +6 -5
  157. package/dist/identity/index.js +4 -3
  158. package/dist/identity/model.d.ts +2 -2
  159. package/dist/identity/notification-settings.d.ts +28 -0
  160. package/dist/identity/notification-settings.js +114 -0
  161. package/dist/identity/notification-settings.js.map +1 -0
  162. package/dist/identity/ports.d.ts +80 -56
  163. package/dist/identity/service.d.ts +8 -7
  164. package/dist/identity/service.js +17 -13
  165. package/dist/identity/service.js.map +1 -1
  166. package/dist/integrations/ports.d.ts +9 -8
  167. package/dist/integrations/service.d.ts +9 -8
  168. package/dist/integrations/service.js +33 -16
  169. package/dist/integrations/service.js.map +1 -1
  170. package/dist/organizations/default-roles.d.ts +22 -0
  171. package/dist/organizations/default-roles.js +89 -0
  172. package/dist/organizations/default-roles.js.map +1 -0
  173. package/dist/organizations/effective-permissions.d.ts +45 -0
  174. package/dist/organizations/effective-permissions.js +113 -0
  175. package/dist/organizations/effective-permissions.js.map +1 -0
  176. package/dist/organizations/events.d.ts +320 -181
  177. package/dist/organizations/events.js +90 -18
  178. package/dist/organizations/events.js.map +1 -1
  179. package/dist/organizations/index.d.ts +15 -10
  180. package/dist/organizations/index.js +8 -3
  181. package/dist/organizations/members.d.ts +66 -8
  182. package/dist/organizations/members.js.map +1 -1
  183. package/dist/organizations/notification-catalog.d.ts +31 -0
  184. package/dist/organizations/notification-catalog.js +32 -0
  185. package/dist/organizations/notification-catalog.js.map +1 -0
  186. package/dist/organizations/notification-categories.d.ts +34 -0
  187. package/dist/organizations/notification-categories.js +66 -0
  188. package/dist/organizations/notification-categories.js.map +1 -0
  189. package/dist/organizations/org-roles.d.ts +33 -0
  190. package/dist/organizations/org-roles.js +49 -0
  191. package/dist/organizations/org-roles.js.map +1 -0
  192. package/dist/organizations/permissions.d.ts +311 -0
  193. package/dist/organizations/permissions.js +327 -0
  194. package/dist/organizations/permissions.js.map +1 -0
  195. package/dist/organizations/ports.d.ts +187 -65
  196. package/dist/organizations/service.d.ts +104 -35
  197. package/dist/organizations/service.js +1247 -323
  198. package/dist/organizations/service.js.map +1 -1
  199. package/dist/organizations/testing.d.ts +18 -0
  200. package/dist/organizations/testing.js +37 -0
  201. package/dist/organizations/testing.js.map +1 -0
  202. package/dist/organizations/types.d.ts +5 -16
  203. package/dist/progress/index.d.ts +2 -2
  204. package/dist/progress/ports.d.ts +11 -3
  205. package/dist/progress/service.d.ts +6 -5
  206. package/dist/progress/service.js +23 -8
  207. package/dist/progress/service.js.map +1 -1
  208. package/dist/reporting/admin/index.d.ts +6 -4
  209. package/dist/reporting/admin/model.d.ts +36 -3
  210. package/dist/reporting/admin/ports.d.ts +39 -14
  211. package/dist/reporting/admin/service.d.ts +32 -11
  212. package/dist/reporting/admin/service.js +102 -18
  213. package/dist/reporting/admin/service.js.map +1 -1
  214. package/dist/reporting/events/index.d.ts +27 -0
  215. package/dist/reporting/events/index.js +20 -0
  216. package/dist/reporting/events/index.js.map +1 -0
  217. package/dist/reporting/member/calendar.d.ts +11 -0
  218. package/dist/reporting/member/calendar.js +75 -0
  219. package/dist/reporting/member/calendar.js.map +1 -0
  220. package/dist/reporting/member/index.d.ts +3 -2
  221. package/dist/reporting/member/model.d.ts +80 -26
  222. package/dist/reporting/member/ports.d.ts +50 -28
  223. package/dist/reporting/member/service.d.ts +81 -35
  224. package/dist/reporting/member/service.js +438 -145
  225. package/dist/reporting/member/service.js.map +1 -1
  226. package/dist/shared/actor.d.ts +22 -3
  227. package/dist/shared/errors.d.ts +15 -2
  228. package/dist/shared/errors.js +21 -2
  229. package/dist/shared/errors.js.map +1 -1
  230. package/dist/shared/id.d.ts +7 -0
  231. package/dist/shared/id.js +8 -1
  232. package/dist/shared/id.js.map +1 -1
  233. package/dist/shared/logger.d.ts +2 -1
  234. package/dist/shared/logger.js +10 -1
  235. package/dist/shared/logger.js.map +1 -1
  236. package/dist/shared/mailer.d.ts +11 -1
  237. package/dist/shared/mailer.js +32 -5
  238. package/dist/shared/mailer.js.map +1 -1
  239. package/dist/shared/notification-categories.d.ts +16 -0
  240. package/dist/shared/notification-categories.js +94 -0
  241. package/dist/shared/notification-categories.js.map +1 -0
  242. package/dist/shared/notification-settings.d.ts +14 -0
  243. package/dist/shared/notification-settings.js +61 -0
  244. package/dist/shared/notification-settings.js.map +1 -0
  245. package/dist/shared/ports.d.ts +68 -2
  246. package/dist/shared/settings.d.ts +2 -1
  247. package/dist/shared/settings.js +1 -0
  248. package/dist/shared/settings.js.map +1 -1
  249. package/dist/types/automations.d.ts +8 -2
  250. package/dist/types/content.d.ts +2 -2
  251. package/dist/types/discussion.d.ts +4 -5
  252. package/dist/types/email-templates.d.ts +119 -6
  253. package/dist/types/entitlements.d.ts +2 -2
  254. package/dist/types/identity.d.ts +14 -17
  255. package/dist/types/index.d.ts +13 -12
  256. package/dist/types/index.js +2 -1
  257. package/dist/types/notifications.d.ts +157 -0
  258. package/dist/types/notifications.js +1 -0
  259. package/dist/types/organizations.d.ts +3 -2
  260. package/dist/types/ports.d.ts +8 -1
  261. package/dist/types/ports.js +7 -1
  262. package/dist/types/ports.js.map +1 -0
  263. package/dist/types/schemas/assets.d.ts +5 -5
  264. package/dist/types/schemas/assets.js +4 -3
  265. package/dist/types/schemas/assets.js.map +1 -1
  266. package/dist/types/schemas/automations.d.ts +1 -10
  267. package/dist/types/schemas/automations.js +1 -3
  268. package/dist/types/schemas/automations.js.map +1 -1
  269. package/dist/types/schemas/content.d.ts +596 -11
  270. package/dist/types/schemas/content.js +225 -9
  271. package/dist/types/schemas/content.js.map +1 -1
  272. package/dist/types/schemas/discussion.d.ts +362 -66
  273. package/dist/types/schemas/discussion.js +123 -15
  274. package/dist/types/schemas/discussion.js.map +1 -1
  275. package/dist/types/schemas/entitlements.d.ts +14 -1
  276. package/dist/types/schemas/entitlements.js +11 -4
  277. package/dist/types/schemas/entitlements.js.map +1 -1
  278. package/dist/types/schemas/identity.d.ts +1 -85
  279. package/dist/types/schemas/identity.js +1 -42
  280. package/dist/types/schemas/identity.js.map +1 -1
  281. package/dist/types/schemas/index.d.ts +8 -7
  282. package/dist/types/schemas/index.js +8 -7
  283. package/dist/types/schemas/notifications.d.ts +96 -0
  284. package/dist/types/schemas/notifications.js +57 -0
  285. package/dist/types/schemas/notifications.js.map +1 -0
  286. package/dist/types/schemas/organizations.d.ts +96 -60
  287. package/dist/types/schemas/organizations.js +78 -27
  288. package/dist/types/schemas/organizations.js.map +1 -1
  289. package/dist/types/schemas/progress.d.ts +0 -1
  290. package/dist/types/schemas/progress.js +0 -1
  291. package/dist/types/schemas/progress.js.map +1 -1
  292. package/package.json +1 -1
  293. package/dist/automations/community-notifications.d.ts +0 -16
  294. package/dist/automations/community-notifications.js +0 -84
  295. package/dist/automations/community-notifications.js.map +0 -1
  296. package/dist/automations/seeds.d.ts +0 -17
  297. package/dist/automations/seeds.js +0 -235
  298. package/dist/automations/seeds.js.map +0 -1
  299. package/dist/identity/notifications.d.ts +0 -27
  300. package/dist/identity/notifications.js +0 -101
  301. package/dist/identity/notifications.js.map +0 -1
  302. package/dist/organizations/access-scopes.d.ts +0 -19
  303. package/dist/organizations/access-scopes.js +0 -89
  304. package/dist/organizations/access-scopes.js.map +0 -1
  305. package/dist/organizations/roles.d.ts +0 -19
  306. package/dist/organizations/roles.js +0 -82
  307. package/dist/organizations/roles.js.map +0 -1
@@ -1,16 +1,45 @@
1
- import { isStaffRole } from "../../organizations/roles.js";
1
+ import { PERMISSION_KEYS } from "../../types/schemas/organizations.js";
2
+ import { NotFoundError } from "../../shared/errors.js";
2
3
  import { seesSpace } from "../../entitlements/rules.js";
4
+ import { channelOrder } from "../../shared/notification-categories.js";
5
+ import { eventSight } from "../../content/event-sight.js";
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";
3
10
  //#region src/reporting/member/service.ts
4
- function ref(id, names) {
5
- const name = id ? names.get(id) : void 0;
6
- return id && name !== void 0 ? {
7
- id,
8
- name
9
- } : null;
11
+ function refKey(ref) {
12
+ return `${ref.type}:${ref.id}`;
13
+ }
14
+ function itemView(ref, titles) {
15
+ const found = ref ? titles[refKey(ref)] : void 0;
16
+ if (!ref || !found) return null;
17
+ return found.excerpt !== void 0 ? {
18
+ type: ref.type,
19
+ id: ref.id,
20
+ excerpt: found.excerpt
21
+ } : {
22
+ type: ref.type,
23
+ id: ref.id,
24
+ title: found.title
25
+ };
10
26
  }
11
27
  function isActivityPublished(settings) {
12
28
  return settings?.published !== false;
13
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;
14
43
  var MemberReportServiceImpl = class {
15
44
  repo;
16
45
  content;
@@ -18,9 +47,15 @@ var MemberReportServiceImpl = class {
18
47
  files;
19
48
  fileUrls;
20
49
  access;
21
- inbox;
22
- posts;
23
- chats;
50
+ people;
51
+ notificationList;
52
+ titles;
53
+ subscriptions;
54
+ notificationCategories;
55
+ notificationSettings;
56
+ channels;
57
+ studentPortalUrl;
58
+ now;
24
59
  constructor(params) {
25
60
  this.repo = params.repo;
26
61
  this.content = params.content;
@@ -28,96 +63,197 @@ var MemberReportServiceImpl = class {
28
63
  this.files = params.files;
29
64
  this.fileUrls = params.fileUrls;
30
65
  this.access = params.access;
31
- this.inbox = params.notifications;
32
- this.posts = params.posts;
33
- this.chats = params.chats;
66
+ this.people = params.people;
67
+ this.notificationList = params.notifications;
68
+ this.titles = params.titles;
69
+ this.subscriptions = params.subscriptions;
70
+ this.notificationCategories = params.notificationCategories;
71
+ this.notificationSettings = params.notificationSettings;
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);
78
+ }
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
+ };
34
90
  }
35
- async notifications(orgId, actor, userId, query) {
36
- const page = await this.inbox.listNotifications(orgId, userId, query);
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);
95
+ const [list, settings, spaceIds, spaces, community] = await Promise.all([
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)
101
+ ]);
102
+ const open = new Set(spaceIds);
37
103
  return {
38
- rows: await this.describeNotifications(orgId, actor, page.rows),
39
- total: page.total,
104
+ communityId,
105
+ digest: community?.settings.digest ?? null,
106
+ categories: this.settingsView(applicableCategories(list.categories, actor.permissions, { communityId }), settings, spaces.filter((space) => open.has(space.id)).map((space) => ({
107
+ spaceId: space.id,
108
+ name: space.name
109
+ })))
110
+ };
111
+ }
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, []) };
116
+ }
117
+ settingsView(categories, settings, spaces) {
118
+ const configured = channelOrder(this.channels.list());
119
+ const channelsAt = (category, spaceId) => {
120
+ const withoutLevel = settings.filter((setting) => setting.category !== category.key || setting.spaceId !== spaceId);
121
+ const fallback = new Map(resolveChannels(category, withoutLevel, spaceId, configured).map((resolved) => [resolved.channel, resolved.enabled]));
122
+ return resolveChannels(category, settings, spaceId, configured).map((resolved) => ({
123
+ ...resolved,
124
+ default: fallback.get(resolved.channel) ?? resolved.enabled
125
+ }));
126
+ };
127
+ return categories.map((category) => {
128
+ const perSpace = category.perSpace === true;
129
+ const view = {
130
+ key: category.key,
131
+ perSpace,
132
+ channels: channelsAt(category, null)
133
+ };
134
+ if (perSpace) view.spaces = spaces.map(({ spaceId, name }) => ({
135
+ spaceId,
136
+ name,
137
+ channels: channelsAt(category, spaceId)
138
+ }));
139
+ return view;
140
+ });
141
+ }
142
+ async notifications(filter, page, actor) {
143
+ const listed = await this.notificationList.list(filter, page, actor);
144
+ return {
145
+ rows: await this.describeNotifications(actor, listed.items),
146
+ total: listed.total,
40
147
  page: page.page,
41
148
  pageSize: page.pageSize,
42
- unreadCount: page.unreadCount
149
+ unreadCount: listed.unreadCount
43
150
  };
44
151
  }
45
- async notificationView(orgId, actor, notification) {
46
- const [view] = await this.describeNotifications(orgId, actor, [notification]);
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);
47
156
  return view;
48
157
  }
49
- async describeNotifications(orgId, actor, rows) {
50
- const ids = (pick) => [...new Set(rows.flatMap((row) => pick(row) ?? []))];
51
- const [people, postTitles, chatTitles, spaces, communities] = await Promise.all([
52
- this.repo.people(orgId, ids((row) => row.actorOrgUserId)),
53
- this.posts.visibleTitles(orgId, actor, ids((row) => row.postId)),
54
- this.chats.titlesFor(orgId, actor, ids((row) => row.chatId)),
55
- Promise.all(ids((row) => row.spaceId).map((id) => this.content.getSpace(orgId, id))),
56
- Promise.all(ids((row) => row.communityId).map((id) => this.content.getCommunity(orgId, id)))
158
+ async notificationUnreadCounts(actor) {
159
+ const communityIds = await this.access.openCommunityIds(actor);
160
+ return this.notificationList.unreadCounts(communityIds, actor);
161
+ }
162
+ async describeNotifications(actor, rows) {
163
+ const orgId = actor.orgId;
164
+ const refs = rows.flatMap((row) => [
165
+ ...row.subject ? [row.subject] : [],
166
+ ...row.parent ? [row.parent] : [],
167
+ ...row.communityId ? [{
168
+ type: "community",
169
+ id: row.communityId
170
+ }] : [],
171
+ ...row.spaceId ? [{
172
+ type: "space",
173
+ id: row.spaceId
174
+ }] : []
175
+ ]);
176
+ const actorIds = [...new Set(rows.flatMap((row) => row.actorId ?? []))];
177
+ const [people, discussionTitles, contentTitles] = await Promise.all([
178
+ this.repo.people(orgId, actorIds),
179
+ this.titles.discussion.titlesFor(refs, actor),
180
+ this.titles.content.titlesFor(refs, actor)
57
181
  ]);
182
+ const titles = {
183
+ ...contentTitles,
184
+ ...discussionTitles
185
+ };
58
186
  const personById = new Map(people.map((person) => [person.orgUserId, person]));
59
- const spaceById = new Map(spaces.flatMap((space) => space ? [[space.id, space.name]] : []));
60
- const communityById = new Map(communities.flatMap((community) => community ? [[community.id, community.title]] : []));
61
- const postById = new Map(Object.entries(postTitles));
62
- const chatById = new Map(Object.entries(chatTitles));
63
187
  return rows.map((row) => {
64
- const person = row.actorOrgUserId ? personById.get(row.actorOrgUserId) : void 0;
65
- const community = ref(row.communityId, communityById);
66
- const space = ref(row.spaceId, spaceById);
67
- const post = ref(row.postId, postById);
68
- const chat = ref(row.chatId, chatById);
188
+ const person = row.actorId ? personById.get(row.actorId) : void 0;
189
+ const community = row.communityId ? titles[`community:${row.communityId}`] : void 0;
190
+ const space = row.spaceId ? titles[`space:${row.spaceId}`] : void 0;
69
191
  return {
70
192
  id: row.id,
71
- kind: row.kind,
72
- createdAt: row.createdAt,
73
- readAt: row.readAt,
193
+ type: row.type,
194
+ scope: row.scope,
195
+ reasons: row.reasons,
196
+ matchedReason: row.matchedReason,
197
+ category: row.category,
198
+ display: row.display,
199
+ community: row.communityId && community ? {
200
+ id: row.communityId,
201
+ title: community.title
202
+ } : null,
203
+ space: row.spaceId && space ? {
204
+ id: row.spaceId,
205
+ name: space.title
206
+ } : null,
74
207
  actor: person ? {
75
208
  orgUserId: person.orgUserId,
76
- displayName: person.displayName,
77
- imageUrl: person.imageAssetId ? this.fileUrls.personImage(person.orgUserId) : null
209
+ name: person.displayName,
210
+ imageUrl: person.imageAssetId ? this.fileUrls.publicImage(orgId, person.orgUserId, person.imageAssetId) : null
78
211
  } : null,
79
- community: community && {
80
- id: community.id,
81
- title: community.name
82
- },
83
- space: space && {
84
- id: space.id,
85
- name: space.name
86
- },
87
- post: post && {
88
- id: post.id,
89
- title: post.name
90
- },
91
- commentId: row.commentId,
92
- chat: chat && {
93
- id: chat.id,
94
- title: chat.name
95
- },
96
- messageId: row.messageId
212
+ subject: itemView(row.subject, titles),
213
+ parent: itemView(row.parent, titles),
214
+ createdAt: row.createdAt,
215
+ readAt: row.readAt
97
216
  };
98
217
  });
99
218
  }
100
- async listCourses(orgId, orgUserId) {
101
- const ids = await this.access.listAccessibleContent(orgId, orgUserId, "course");
102
- 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;
230
+ }
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");
103
235
  }
104
- async getCourse(orgId, orgUserId, courseId) {
105
- if (!await this.access.hasAccess(orgId, orgUserId, courseId)) return null;
106
- const course = await this.content.getCourse(orgId, courseId);
107
- return course && course.status === "published" ? course : null;
236
+ async requireCourse(courseId, actor) {
237
+ const course = await this.opensCourse(courseId, actor);
238
+ if (!course) throw new NotFoundError("Course", courseId);
239
+ return course;
108
240
  }
109
- async listModules(orgId, orgUserId, courseId) {
110
- if (!await this.getCourse(orgId, orgUserId, courseId)) return null;
111
- return this.content.listCourseModules(orgId, courseId);
241
+ getCourse(courseId, actor) {
242
+ return this.requireCourse(courseId, actor);
112
243
  }
113
- async listActivities(orgId, orgUserId, courseId) {
114
- if (!await this.getCourse(orgId, orgUserId, courseId)) return null;
115
- return (await this.content.listCourseActivityViews(orgId, courseId)).filter((a) => isActivityPublished(a.settings));
244
+ async listModules(courseId, actor) {
245
+ await this.requireCourse(courseId, actor);
246
+ return this.content.listCourseModules(courseId, this.system(actor));
116
247
  }
117
- async courseProgress(orgId, orgUserId, courseId) {
118
- if (!await this.getCourse(orgId, orgUserId, courseId)) return null;
119
- const ids = (await this.content.listCourseActivities(orgId, courseId)).filter((a) => isActivityPublished(a.settings)).map((a) => a.id);
120
- const records = await this.progress.listByTargets(orgId, orgUserId, ids);
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);
121
257
  const activities = {};
122
258
  const positions = {};
123
259
  let done = 0;
@@ -128,7 +264,7 @@ var MemberReportServiceImpl = class {
128
264
  if (r.position != null) positions[r.targetId] = r.position;
129
265
  }
130
266
  const courseRecord = await this.progress.get(orgId, {
131
- orgUserId,
267
+ orgUserId: actor.id,
132
268
  targetType: "course",
133
269
  targetId: courseId
134
270
  });
@@ -139,85 +275,236 @@ var MemberReportServiceImpl = class {
139
275
  positions
140
276
  };
141
277
  }
142
- async listDownloads(orgId, orgUserId) {
143
- const ids = await this.access.listAccessibleContent(orgId, orgUserId, "download");
144
- 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");
145
282
  }
146
- async getDownload(orgId, orgUserId, downloadId) {
147
- if (!await this.access.hasAccess(orgId, orgUserId, downloadId)) return null;
148
- const download = await this.content.getDownload(orgId, downloadId);
149
- 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);
150
286
  return {
151
287
  download,
152
- assets: await this.content.listDownloadAssets(orgId, downloadId)
288
+ assets: await this.content.listDownloadAssets(downloadId, this.system(actor))
153
289
  };
154
290
  }
155
- async thumbnailFile(orgId, actor, owner, id) {
156
- const item = owner === "course" ? await this.content.getCourse(orgId, id) : owner === "download" ? await this.content.getDownload(orgId, id) : await this.content.getCommunity(orgId, id);
157
- if (!item?.thumbnailAssetId || item.status !== "published" && !isStaffRole(actor.role)) return null;
158
- 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);
159
296
  }
160
- async activityFile(orgId, actor, activityId, assetId) {
161
- const activity = await this.content.getActivity(orgId, activityId);
162
- if (!activity) return null;
163
- if (!isStaffRole(actor.role)) {
164
- 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);
165
303
  }
166
- if (!(await this.content.listCourseActivityAssets(orgId, activity.courseId)).some((link) => link.activityId === activityId && link.assetId === assetId)) return null;
167
- return this.files.signFile(orgId, assetId);
168
- }
169
- async downloadFile(orgId, actor, downloadId, assetId) {
170
- if (!isStaffRole(actor.role) && !await this.getDownload(orgId, actor.id, downloadId)) return null;
171
- const file = (await this.content.listDownloadAssets(orgId, downloadId)).find((f) => f.assetId === assetId);
172
- if (!file) return null;
173
- return this.files.signFile(orgId, assetId, { saveAs: file.displayName ?? file.filename });
174
- }
175
- async personImage(orgId, orgUserId) {
176
- const [person] = await this.repo.people(orgId, [orgUserId]);
177
- if (!person?.imageAssetId) return null;
178
- return this.files.signFile(orgId, person.imageAssetId);
179
- }
180
- async directory(orgId, actor, communityId, query) {
181
- if (!await this.access.isCommunityMember(orgId, actor, communityId)) return null;
182
- if (!await this.content.getCommunity(orgId, communityId)) return null;
183
- 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);
184
411
  return {
185
412
  ...page,
186
- rows: page.rows.map((row) => ({
187
- ...row,
188
- imageUrl: row.imageAssetId ? this.fileUrls.personImage(row.orgUserId) : null
189
- }))
413
+ rows
190
414
  };
191
415
  }
192
- async communityHome(orgId, actor, communityId) {
193
- if (!await this.access.isCommunityMember(orgId, actor, communityId)) return null;
194
- const community = await this.content.getCommunity(orgId, communityId);
195
- if (!community) return null;
196
- 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;
197
472
  return {
198
473
  communityId,
199
474
  title: community.title,
200
475
  description: community.description,
201
476
  thumbnailUrl: community.thumbnailUrl,
202
477
  dm: community.settings.dm,
203
- 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
+ }
204
486
  };
205
487
  }
206
- async communitySpace(orgId, actor, communityId, spaceId) {
207
- if (!await this.access.isCommunityMember(orgId, actor, communityId)) return null;
208
- const space = await this.content.getSpace(orgId, spaceId);
209
- if (!space || space.communityId !== communityId) return null;
210
- const [view] = await this.homeSpaces(orgId, actor, communityId, [space]);
211
- return view ?? null;
212
- }
213
- async homeSpaces(orgId, actor, communityId, spaces) {
214
- const [openIds, counts, standings] = await Promise.all([
215
- this.access.openSpaceIds(orgId, actor, communityId),
216
- this.repo.spaceMemberCounts(orgId, communityId),
217
- this.access.spaceStandings(orgId, actor, communityId)
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;
496
+ }
497
+ async homeSpaces(actor, communityId, spaces, everySpace) {
498
+ const orgId = actor.orgId;
499
+ const excluding = await this.access.everySpaceOpeners(orgId, communityId);
500
+ const [openIds, counts, standings, subscribed] = await Promise.all([
501
+ this.access.openSpaceIds(communityId, actor),
502
+ this.repo.spaceMemberCounts(orgId, communityId, excluding),
503
+ this.access.spaceStandings(communityId, actor),
504
+ this.subscriptions.viewerSubscriptions(orgId, actor.id, "space", spaces.map((space) => space.id), /* @__PURE__ */ new Date())
218
505
  ]);
219
506
  const open = new Set(openIds);
220
- 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]));
221
508
  return Promise.all(seen.map(async (space) => {
222
509
  const memberCount = counts[space.id] ?? 0;
223
510
  const base = {
@@ -241,29 +528,31 @@ var MemberReportServiceImpl = class {
241
528
  id: topic.id,
242
529
  name: topic.name
243
530
  })),
244
- links: await this.linkViews(orgId, actor, space.links)
531
+ links: await this.linkViews(actor, space.links),
532
+ subscription: subscribed[space.id] ?? null
245
533
  };
246
534
  }));
247
535
  }
248
- async linkViews(orgId, actor, links) {
249
- 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);
250
539
  return (await Promise.all(links.map(async (link) => {
251
- const course = await this.content.getCourse(orgId, link.contentId);
252
- 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) ? {
253
542
  id: link.id,
254
543
  contentId: link.contentId,
255
544
  type: "course",
256
545
  title: course.title
257
546
  } : null;
258
- const download = await this.content.getDownload(orgId, link.contentId);
259
- 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) ? {
260
549
  id: link.id,
261
550
  contentId: link.contentId,
262
551
  type: "download",
263
552
  title: download.title
264
553
  } : null;
265
- const community = await this.content.getCommunity(orgId, link.contentId);
266
- 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) ? {
267
556
  id: link.id,
268
557
  contentId: link.contentId,
269
558
  type: "community",
@@ -273,6 +562,10 @@ var MemberReportServiceImpl = class {
273
562
  }))).filter((view) => view !== null);
274
563
  }
275
564
  };
565
+ function signed(url, what, id) {
566
+ if (!url) throw new NotFoundError(what, id);
567
+ return url;
568
+ }
276
569
  function standingOf(space, open, memberCount, status) {
277
570
  if (space.accessRule === "all_members" || open && status !== "active") return "open";
278
571
  if (status === "active") return "member";