@headless-lms/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/events.d.ts +24 -18
- package/dist/assets/file-urls.d.ts +11 -7
- package/dist/assets/file-urls.js +14 -4
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +5 -4
- package/dist/assets/index.js +4 -3
- package/dist/assets/jobs.d.ts +15 -0
- package/dist/assets/jobs.js +30 -0
- package/dist/assets/jobs.js.map +1 -0
- package/dist/assets/ports.d.ts +37 -17
- package/dist/assets/service.d.ts +30 -15
- package/dist/assets/service.js +229 -59
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js +2 -63
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.d.ts +1 -2
- package/dist/automations/catalog.js +137 -65
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +20 -20
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +36 -20
- package/dist/automations/community-mail.js +32 -24
- package/dist/automations/community-mail.js.map +1 -1
- package/dist/automations/event-permissions.d.ts +12 -0
- package/dist/automations/event-permissions.js +38 -0
- package/dist/automations/event-permissions.js.map +1 -0
- package/dist/automations/events.d.ts +0 -10
- package/dist/automations/index.d.ts +12 -4
- package/dist/automations/index.js +9 -1
- package/dist/automations/notifications/applicable.d.ts +8 -0
- package/dist/automations/notifications/applicable.js +12 -0
- package/dist/automations/notifications/applicable.js.map +1 -0
- package/dist/automations/notifications/debounce.d.ts +25 -0
- package/dist/automations/notifications/debounce.js +93 -0
- package/dist/automations/notifications/debounce.js.map +1 -0
- package/dist/automations/notifications/defaults.d.ts +8 -0
- package/dist/automations/notifications/defaults.js +346 -0
- package/dist/automations/notifications/defaults.js.map +1 -0
- package/dist/automations/notifications/delivery.d.ts +28 -0
- package/dist/automations/notifications/delivery.js +88 -0
- package/dist/automations/notifications/delivery.js.map +1 -0
- package/dist/automations/notifications/processor.d.ts +64 -0
- package/dist/automations/notifications/processor.js +270 -0
- package/dist/automations/notifications/processor.js.map +1 -0
- package/dist/automations/notifications/rules/content.d.ts +6 -0
- package/dist/automations/notifications/rules/content.js +96 -0
- package/dist/automations/notifications/rules/content.js.map +1 -0
- package/dist/automations/notifications/rules/discussion.d.ts +7 -0
- package/dist/automations/notifications/rules/discussion.js +678 -0
- package/dist/automations/notifications/rules/discussion.js.map +1 -0
- package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
- package/dist/automations/notifications/rules/entitlements.js +276 -0
- package/dist/automations/notifications/rules/entitlements.js.map +1 -0
- package/dist/automations/notifications/rules/events.d.ts +9 -0
- package/dist/automations/notifications/rules/events.js +27 -0
- package/dist/automations/notifications/rules/events.js.map +1 -0
- package/dist/automations/notifications/rules/index.d.ts +6 -0
- package/dist/automations/notifications/rules/index.js +15 -0
- package/dist/automations/notifications/rules/index.js.map +1 -0
- package/dist/automations/notifications/rules/organizations.d.ts +6 -0
- package/dist/automations/notifications/rules/organizations.js +35 -0
- package/dist/automations/notifications/rules/organizations.js.map +1 -0
- package/dist/automations/notifications/rules/recipients.d.ts +32 -0
- package/dist/automations/notifications/rules/recipients.js +98 -0
- package/dist/automations/notifications/rules/recipients.js.map +1 -0
- package/dist/automations/notifications/service.d.ts +29 -0
- package/dist/automations/notifications/service.js +45 -0
- package/dist/automations/notifications/service.js.map +1 -0
- package/dist/automations/notifications/types.d.ts +7 -0
- package/dist/automations/notifications/types.js +560 -0
- package/dist/automations/notifications/types.js.map +1 -0
- package/dist/automations/ports.d.ts +134 -21
- package/dist/automations/service.d.ts +30 -30
- package/dist/automations/service.js +111 -144
- package/dist/automations/service.js.map +1 -1
- package/dist/content/event-sight.d.ts +11 -0
- package/dist/content/event-sight.js +11 -0
- package/dist/content/event-sight.js.map +1 -0
- package/dist/content/events.d.ts +437 -46
- package/dist/content/events.js +45 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +8 -4
- package/dist/content/index.js +5 -1
- package/dist/content/jobs.d.ts +16 -0
- package/dist/content/jobs.js +37 -0
- package/dist/content/jobs.js.map +1 -0
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +37 -0
- package/dist/content/notification-categories.js +73 -0
- package/dist/content/notification-categories.js.map +1 -0
- package/dist/content/ports.d.ts +132 -68
- package/dist/content/service.d.ts +100 -68
- package/dist/content/service.js +703 -146
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +25 -0
- package/dist/content/titles.js +82 -0
- package/dist/content/titles.js.map +1 -0
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +4 -2
- package/dist/discussion/authors.js +9 -5
- package/dist/discussion/authors.js.map +1 -1
- package/dist/discussion/chats.d.ts +63 -39
- package/dist/discussion/chats.js +455 -190
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/digests.d.ts +24 -0
- package/dist/discussion/digests.js +104 -0
- package/dist/discussion/digests.js.map +1 -0
- package/dist/discussion/events.d.ts +227 -23
- package/dist/discussion/events.js +114 -19
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +12 -4
- package/dist/discussion/index.js +9 -2
- package/dist/discussion/jobs.d.ts +16 -0
- package/dist/discussion/jobs.js +40 -0
- package/dist/discussion/jobs.js.map +1 -0
- package/dist/discussion/live.d.ts +106 -0
- package/dist/discussion/live.js +642 -0
- package/dist/discussion/live.js.map +1 -0
- package/dist/discussion/mentions.d.ts +11 -0
- package/dist/discussion/mentions.js +35 -0
- package/dist/discussion/mentions.js.map +1 -0
- package/dist/discussion/ports.d.ts +279 -114
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +60 -44
- package/dist/discussion/posts.js +453 -287
- package/dist/discussion/posts.js.map +1 -1
- package/dist/discussion/rules.d.ts +18 -0
- package/dist/discussion/rules.js +22 -0
- package/dist/discussion/rules.js.map +1 -0
- package/dist/discussion/service.d.ts +50 -35
- package/dist/discussion/service.js +333 -205
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +37 -0
- package/dist/discussion/subscriptions.js +115 -0
- package/dist/discussion/subscriptions.js.map +1 -0
- package/dist/discussion/titles.d.ts +28 -0
- package/dist/discussion/titles.js +98 -0
- package/dist/discussion/titles.js.map +1 -0
- package/dist/entitlements/events.d.ts +20 -20
- package/dist/entitlements/index.d.ts +5 -4
- package/dist/entitlements/index.js +3 -2
- package/dist/entitlements/jobs.d.ts +12 -0
- package/dist/entitlements/jobs.js +18 -0
- package/dist/entitlements/jobs.js.map +1 -0
- package/dist/entitlements/model.d.ts +2 -2
- package/dist/entitlements/ports.d.ts +68 -38
- package/dist/entitlements/rules.d.ts +3 -6
- package/dist/entitlements/rules.js +5 -10
- package/dist/entitlements/rules.js.map +1 -1
- package/dist/entitlements/service.d.ts +43 -28
- package/dist/entitlements/service.js +387 -160
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +30 -0
- package/dist/identity/devices.js +109 -0
- package/dist/identity/devices.js.map +1 -0
- package/dist/identity/index.d.ts +6 -5
- package/dist/identity/index.js +4 -3
- package/dist/identity/model.d.ts +2 -2
- package/dist/identity/notification-settings.d.ts +28 -0
- package/dist/identity/notification-settings.js +114 -0
- package/dist/identity/notification-settings.js.map +1 -0
- package/dist/identity/ports.d.ts +80 -56
- package/dist/identity/service.d.ts +8 -7
- package/dist/identity/service.js +17 -13
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/ports.d.ts +9 -8
- package/dist/integrations/service.d.ts +9 -8
- package/dist/integrations/service.js +33 -16
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/default-roles.d.ts +22 -0
- package/dist/organizations/default-roles.js +89 -0
- package/dist/organizations/default-roles.js.map +1 -0
- package/dist/organizations/effective-permissions.d.ts +45 -0
- package/dist/organizations/effective-permissions.js +113 -0
- package/dist/organizations/effective-permissions.js.map +1 -0
- package/dist/organizations/events.d.ts +320 -181
- package/dist/organizations/events.js +90 -18
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +15 -10
- package/dist/organizations/index.js +8 -3
- package/dist/organizations/members.d.ts +66 -8
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/notification-catalog.d.ts +31 -0
- package/dist/organizations/notification-catalog.js +32 -0
- package/dist/organizations/notification-catalog.js.map +1 -0
- package/dist/organizations/notification-categories.d.ts +34 -0
- package/dist/organizations/notification-categories.js +66 -0
- package/dist/organizations/notification-categories.js.map +1 -0
- package/dist/organizations/org-roles.d.ts +33 -0
- package/dist/organizations/org-roles.js +49 -0
- package/dist/organizations/org-roles.js.map +1 -0
- package/dist/organizations/permissions.d.ts +311 -0
- package/dist/organizations/permissions.js +327 -0
- package/dist/organizations/permissions.js.map +1 -0
- package/dist/organizations/ports.d.ts +187 -65
- package/dist/organizations/service.d.ts +104 -35
- package/dist/organizations/service.js +1247 -323
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/testing.d.ts +18 -0
- package/dist/organizations/testing.js +37 -0
- package/dist/organizations/testing.js.map +1 -0
- package/dist/organizations/types.d.ts +5 -16
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +11 -3
- package/dist/progress/service.d.ts +6 -5
- package/dist/progress/service.js +23 -8
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +6 -4
- package/dist/reporting/admin/model.d.ts +36 -3
- package/dist/reporting/admin/ports.d.ts +39 -14
- package/dist/reporting/admin/service.d.ts +32 -11
- package/dist/reporting/admin/service.js +102 -18
- package/dist/reporting/admin/service.js.map +1 -1
- package/dist/reporting/events/index.d.ts +27 -0
- package/dist/reporting/events/index.js +20 -0
- package/dist/reporting/events/index.js.map +1 -0
- package/dist/reporting/member/calendar.d.ts +11 -0
- package/dist/reporting/member/calendar.js +75 -0
- package/dist/reporting/member/calendar.js.map +1 -0
- package/dist/reporting/member/index.d.ts +3 -2
- package/dist/reporting/member/model.d.ts +80 -26
- package/dist/reporting/member/ports.d.ts +50 -28
- package/dist/reporting/member/service.d.ts +81 -35
- package/dist/reporting/member/service.js +438 -145
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +22 -3
- package/dist/shared/errors.d.ts +15 -2
- package/dist/shared/errors.js +21 -2
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +7 -0
- package/dist/shared/id.js +8 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/logger.d.ts +2 -1
- package/dist/shared/logger.js +10 -1
- package/dist/shared/logger.js.map +1 -1
- package/dist/shared/mailer.d.ts +11 -1
- package/dist/shared/mailer.js +32 -5
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/notification-categories.d.ts +16 -0
- package/dist/shared/notification-categories.js +94 -0
- package/dist/shared/notification-categories.js.map +1 -0
- package/dist/shared/notification-settings.d.ts +14 -0
- package/dist/shared/notification-settings.js +61 -0
- package/dist/shared/notification-settings.js.map +1 -0
- package/dist/shared/ports.d.ts +68 -2
- package/dist/shared/settings.d.ts +2 -1
- package/dist/shared/settings.js +1 -0
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/automations.d.ts +8 -2
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +4 -5
- package/dist/types/email-templates.d.ts +119 -6
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +14 -17
- package/dist/types/index.d.ts +13 -12
- package/dist/types/index.js +2 -1
- package/dist/types/notifications.d.ts +157 -0
- package/dist/types/notifications.js +1 -0
- package/dist/types/organizations.d.ts +3 -2
- package/dist/types/ports.d.ts +8 -1
- package/dist/types/ports.js +7 -1
- package/dist/types/ports.js.map +1 -0
- package/dist/types/schemas/assets.d.ts +5 -5
- package/dist/types/schemas/assets.js +4 -3
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +1 -10
- package/dist/types/schemas/automations.js +1 -3
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +596 -11
- package/dist/types/schemas/content.js +225 -9
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +362 -66
- package/dist/types/schemas/discussion.js +123 -15
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +14 -1
- package/dist/types/schemas/entitlements.js +11 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +1 -85
- package/dist/types/schemas/identity.js +1 -42
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +8 -7
- package/dist/types/schemas/index.js +8 -7
- package/dist/types/schemas/notifications.d.ts +96 -0
- package/dist/types/schemas/notifications.js +57 -0
- package/dist/types/schemas/notifications.js.map +1 -0
- package/dist/types/schemas/organizations.d.ts +96 -60
- package/dist/types/schemas/organizations.js +78 -27
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +0 -1
- package/dist/types/schemas/progress.js +0 -1
- package/dist/types/schemas/progress.js.map +1 -1
- package/package.json +1 -1
- package/dist/automations/community-notifications.d.ts +0 -16
- package/dist/automations/community-notifications.js +0 -84
- package/dist/automations/community-notifications.js.map +0 -1
- package/dist/automations/seeds.d.ts +0 -17
- package/dist/automations/seeds.js +0 -235
- package/dist/automations/seeds.js.map +0 -1
- package/dist/identity/notifications.d.ts +0 -27
- package/dist/identity/notifications.js +0 -101
- package/dist/identity/notifications.js.map +0 -1
- package/dist/organizations/access-scopes.d.ts +0 -19
- package/dist/organizations/access-scopes.js +0 -89
- package/dist/organizations/access-scopes.js.map +0 -1
- package/dist/organizations/roles.d.ts +0 -19
- package/dist/organizations/roles.js +0 -82
- package/dist/organizations/roles.js.map +0 -1
|
@@ -1,31 +1,59 @@
|
|
|
1
|
-
import { ConflictError, ForbiddenError, NotFoundError } from "../shared/errors.js";
|
|
2
1
|
import { noopLogger } from "../shared/logger.js";
|
|
3
|
-
import {
|
|
2
|
+
import { ConflictError, ForbiddenError, NotFoundError } from "../shared/errors.js";
|
|
4
3
|
import { entitlementEvents } from "./events.js";
|
|
5
|
-
import {
|
|
4
|
+
import { opensSpace, seesSpace } from "./rules.js";
|
|
6
5
|
//#region src/entitlements/service.ts
|
|
7
6
|
const ENDING_SOON_MS = 10080 * 60 * 1e3;
|
|
8
7
|
const NOTICE_BATCH_SIZE = 100;
|
|
9
8
|
var EntitlementsServiceImpl = class {
|
|
10
9
|
repo;
|
|
11
10
|
content;
|
|
11
|
+
permissions;
|
|
12
12
|
uow;
|
|
13
13
|
logger;
|
|
14
14
|
constructor(params) {
|
|
15
15
|
this.repo = params.repo;
|
|
16
16
|
this.content = params.content;
|
|
17
|
+
this.permissions = params.permissions;
|
|
17
18
|
this.uow = params.uow;
|
|
18
19
|
this.logger = params.logger ?? noopLogger;
|
|
19
20
|
}
|
|
20
|
-
list(
|
|
21
|
+
async list(query, actor) {
|
|
22
|
+
const orgId = actor.orgId;
|
|
23
|
+
if (!actor.permissions.has("access:read")) {
|
|
24
|
+
const contentId = query.contentId;
|
|
25
|
+
if (!(contentId !== void 0 && actor.permissions.has("progress:read", contentId) && await this.repo.contentTypeOf(orgId, contentId) === "course")) {
|
|
26
|
+
this.logger.warn("entitlements list refused", {
|
|
27
|
+
orgId,
|
|
28
|
+
actorId: actor.id,
|
|
29
|
+
contentId: contentId ?? null
|
|
30
|
+
});
|
|
31
|
+
throw new ForbiddenError(contentId === void 0 ? "You need access:read" : "You need access:read, or progress:read on this course, to see who holds it");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
21
34
|
return this.repo.list(orgId, query);
|
|
22
35
|
}
|
|
23
|
-
async grant(
|
|
36
|
+
async grant(input, actor) {
|
|
37
|
+
const orgId = actor.orgId;
|
|
38
|
+
const { contentType, ...grant } = input;
|
|
39
|
+
const contentId = grant.contentId ?? null;
|
|
40
|
+
const type = contentId === null ? null : await this.repo.contentTypeOf(orgId, contentId);
|
|
41
|
+
if (contentType !== void 0 && type !== contentType) throw new NotFoundError(contentType === "event" ? "Event" : "Content", contentId ?? "");
|
|
42
|
+
if (contentId !== null && type === "event") return this.grantEvent({
|
|
43
|
+
...grant,
|
|
44
|
+
contentId
|
|
45
|
+
}, actor);
|
|
46
|
+
actor.permissions.require("access:write");
|
|
47
|
+
if (contentId !== null && type === null) throw new NotFoundError("Content", contentId);
|
|
24
48
|
const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {
|
|
25
|
-
const created = await entitlements.insert(orgId,
|
|
49
|
+
const created = await entitlements.insert(orgId, {
|
|
50
|
+
...grant,
|
|
51
|
+
source: this.sourceOf(actor, grant.orgUserId)
|
|
52
|
+
});
|
|
26
53
|
await outbox.append([entitlementEvents.entitlementCreated.make({
|
|
27
54
|
orgId,
|
|
28
|
-
data: created
|
|
55
|
+
data: created,
|
|
56
|
+
metadata: this.actorMetadata(actor)
|
|
29
57
|
})]);
|
|
30
58
|
return created;
|
|
31
59
|
});
|
|
@@ -34,73 +62,144 @@ var EntitlementsServiceImpl = class {
|
|
|
34
62
|
entitlementId: entitlement.id,
|
|
35
63
|
orgUserId: entitlement.orgUserId,
|
|
36
64
|
bundleId: entitlement.bundleId,
|
|
37
|
-
contentId: entitlement.contentId
|
|
65
|
+
contentId: entitlement.contentId,
|
|
66
|
+
source: entitlement.source,
|
|
67
|
+
actorId: actor.id
|
|
38
68
|
});
|
|
39
69
|
return entitlement;
|
|
40
70
|
}
|
|
41
|
-
async
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
async revoke(input, actor) {
|
|
72
|
+
const orgId = actor.orgId;
|
|
73
|
+
const { orgUserId } = input;
|
|
74
|
+
const contentId = input.contentId ?? null;
|
|
75
|
+
const bundleId = input.bundleId ?? null;
|
|
76
|
+
const type = contentId === null ? null : await this.repo.contentTypeOf(orgId, contentId);
|
|
77
|
+
if (input.contentType !== void 0 && type !== input.contentType) throw new NotFoundError(input.contentType === "event" ? "Event" : "Content", contentId ?? "");
|
|
78
|
+
if (type === "event" && contentId !== null) {
|
|
79
|
+
if (orgUserId !== actor.id) this.requireAttendeeManager(actor, contentId);
|
|
80
|
+
} else {
|
|
81
|
+
actor.permissions.require("access:write");
|
|
82
|
+
if (contentId !== null && type === null) throw new NotFoundError("Content", contentId);
|
|
83
|
+
}
|
|
84
|
+
const target = contentId ?? bundleId;
|
|
85
|
+
const revoked = await this.uow.run(async ({ entitlements, outbox }) => {
|
|
86
|
+
const existing = await entitlements.lockGrant(orgId, {
|
|
87
|
+
orgUserId,
|
|
88
|
+
contentId,
|
|
89
|
+
bundleId
|
|
90
|
+
});
|
|
91
|
+
if (!existing || !activeNow(existing, /* @__PURE__ */ new Date())) {
|
|
92
|
+
this.logger.warn("entitlement revoke rejected — no direct active grant", {
|
|
46
93
|
orgId,
|
|
47
|
-
|
|
48
|
-
|
|
94
|
+
orgUserId,
|
|
95
|
+
contentId,
|
|
96
|
+
bundleId,
|
|
97
|
+
actorId: actor.id
|
|
98
|
+
});
|
|
99
|
+
throw new NotFoundError("Entitlement", `${orgUserId}:${target}`);
|
|
100
|
+
}
|
|
101
|
+
const updated = await entitlements.setStatus(orgId, existing.id, "revoked");
|
|
102
|
+
if (!updated) throw new NotFoundError("Entitlement", existing.id);
|
|
103
|
+
await outbox.append([entitlementEvents.entitlementDeleted.make({
|
|
104
|
+
orgId,
|
|
105
|
+
data: updated,
|
|
106
|
+
metadata: this.actorMetadata(actor)
|
|
107
|
+
})]);
|
|
108
|
+
return updated;
|
|
109
|
+
});
|
|
110
|
+
this.logger.info("entitlement revoked", {
|
|
111
|
+
orgId,
|
|
112
|
+
entitlementId: revoked.id,
|
|
113
|
+
orgUserId,
|
|
114
|
+
contentId,
|
|
115
|
+
bundleId,
|
|
116
|
+
actorId: actor.id
|
|
117
|
+
});
|
|
118
|
+
return revoked;
|
|
119
|
+
}
|
|
120
|
+
async grantEvent(input, actor) {
|
|
121
|
+
const orgId = actor.orgId;
|
|
122
|
+
const eventId = input.contentId;
|
|
123
|
+
const orgUserId = input.orgUserId;
|
|
124
|
+
const self = orgUserId === actor.id;
|
|
125
|
+
if (!self) this.requireAttendeeManager(actor, eventId);
|
|
126
|
+
const event = await this.content.getEvent(eventId, this.system(orgId));
|
|
127
|
+
const now = /* @__PURE__ */ new Date();
|
|
128
|
+
if (self) await this.assertCanRegister(event, actor, now);
|
|
129
|
+
else assertOpenForAttendees(event, now);
|
|
130
|
+
const { entitlement, created } = await this.uow.run(async ({ entitlements, outbox }) => {
|
|
131
|
+
if (event.settings.capacity !== null) await entitlements.lockCapacity(orgId, eventId);
|
|
132
|
+
const existing = await entitlements.lockGrant(orgId, {
|
|
133
|
+
orgUserId,
|
|
134
|
+
contentId: eventId
|
|
135
|
+
});
|
|
136
|
+
if (existing && activeNow(existing, now)) {
|
|
137
|
+
if (self) throw alreadyRegistered();
|
|
49
138
|
return {
|
|
50
|
-
entitlement:
|
|
51
|
-
|
|
139
|
+
entitlement: existing,
|
|
140
|
+
created: false
|
|
52
141
|
};
|
|
53
142
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
entitlement: existing,
|
|
63
|
-
change: null
|
|
64
|
-
};
|
|
65
|
-
const updated = await entitlements.setExpiresAt(orgId, existing.id, expiresAt);
|
|
66
|
-
if (!updated) throw new NotFoundError("Entitlement", existing.id);
|
|
67
|
-
await outbox.append([entitlementEvents.entitlementUpdated.make({
|
|
143
|
+
await this.assertPlace(entitlements, orgId, event, orgUserId);
|
|
144
|
+
const inserted = await entitlements.insert(orgId, {
|
|
145
|
+
orgUserId,
|
|
146
|
+
contentId: eventId,
|
|
147
|
+
expiresAt: self ? null : input.expiresAt ?? null,
|
|
148
|
+
source: this.sourceOf(actor, orgUserId)
|
|
149
|
+
});
|
|
150
|
+
await outbox.append([entitlementEvents.entitlementCreated.make({
|
|
68
151
|
orgId,
|
|
69
|
-
data:
|
|
152
|
+
data: inserted,
|
|
153
|
+
metadata: this.actorMetadata(actor)
|
|
70
154
|
})]);
|
|
71
155
|
return {
|
|
72
|
-
entitlement:
|
|
73
|
-
|
|
156
|
+
entitlement: inserted,
|
|
157
|
+
created: true
|
|
74
158
|
};
|
|
75
159
|
});
|
|
76
|
-
if (
|
|
160
|
+
if (created) this.logger.info("entitlement granted", {
|
|
77
161
|
orgId,
|
|
78
162
|
entitlementId: entitlement.id,
|
|
79
|
-
orgUserId
|
|
80
|
-
|
|
81
|
-
|
|
163
|
+
orgUserId,
|
|
164
|
+
contentId: eventId,
|
|
165
|
+
source: entitlement.source,
|
|
166
|
+
actorId: actor.id
|
|
82
167
|
});
|
|
83
168
|
return entitlement;
|
|
84
169
|
}
|
|
85
|
-
async
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
orgId,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
170
|
+
async assertCanRegister(event, actor, now) {
|
|
171
|
+
if (event.status !== "published") throw new NotFoundError("Event", event.id);
|
|
172
|
+
if (await this.repo.hasAccess(actor.orgId, actor.id, event.id)) throw alreadyRegistered();
|
|
173
|
+
if (event.settings.type !== "public") {
|
|
174
|
+
this.logger.warn("event registration refused — private", {
|
|
175
|
+
orgId: actor.orgId,
|
|
176
|
+
eventId: event.id,
|
|
177
|
+
actorId: actor.id
|
|
178
|
+
});
|
|
179
|
+
throw new ForbiddenError("This event is private: only its organizers can add attendees");
|
|
180
|
+
}
|
|
181
|
+
if (event.startsAt.getTime() <= now.getTime()) throw new ConflictError("This event has already started", "started");
|
|
182
|
+
}
|
|
183
|
+
requireAttendeeManager(actor, eventId) {
|
|
184
|
+
if (actor.permissions.has("access:write") || actor.permissions.has("events:write", eventId)) return;
|
|
185
|
+
this.logger.warn("event attendees refused — cannot manage", {
|
|
186
|
+
orgId: actor.orgId,
|
|
187
|
+
eventId,
|
|
188
|
+
actorId: actor.id
|
|
102
189
|
});
|
|
103
|
-
|
|
190
|
+
throw new ForbiddenError("You need access:write, or events:write on this event");
|
|
191
|
+
}
|
|
192
|
+
async assertPlace(entitlements, orgId, event, orgUserId) {
|
|
193
|
+
const capacity = event.settings.capacity;
|
|
194
|
+
if (capacity === null) return;
|
|
195
|
+
if ((await entitlements.grantedOrgUserIds(orgId, event.id)).filter((id) => id !== orgUserId).length >= capacity) throw new ConflictError("This event is full", "full");
|
|
196
|
+
}
|
|
197
|
+
sourceOf(actor, orgUserId) {
|
|
198
|
+
if (actor.id === this.system(actor.orgId).id) return "automation";
|
|
199
|
+
return orgUserId === actor.id ? "self" : "manual";
|
|
200
|
+
}
|
|
201
|
+
actorMetadata(actor) {
|
|
202
|
+
return actor.id === this.system(actor.orgId).id ? {} : { actorId: actor.id };
|
|
104
203
|
}
|
|
105
204
|
hasAccess(orgId, orgUserId, contentId) {
|
|
106
205
|
return this.repo.hasAccess(orgId, orgUserId, contentId);
|
|
@@ -108,66 +207,104 @@ var EntitlementsServiceImpl = class {
|
|
|
108
207
|
listAccessibleContent(orgId, orgUserId, type) {
|
|
109
208
|
return this.repo.listAccessibleContent(orgId, orgUserId, type);
|
|
110
209
|
}
|
|
111
|
-
|
|
112
|
-
return
|
|
210
|
+
holdsAnyGrant(orgId, orgUserId) {
|
|
211
|
+
return this.repo.holdsAnyGrant(orgId, orgUserId);
|
|
212
|
+
}
|
|
213
|
+
async opensEverySpace(actor, communityId) {
|
|
214
|
+
return (await this.standingOf(actor, communityId, null)).everySpace;
|
|
113
215
|
}
|
|
114
|
-
async
|
|
115
|
-
|
|
216
|
+
async isCommunityMember(communityId, actor) {
|
|
217
|
+
if (actor.permissions.has("communities:read", communityId)) return true;
|
|
218
|
+
return this.repo.hasCommunityAccess(actor.orgId, actor.id, communityId);
|
|
219
|
+
}
|
|
220
|
+
async canOpenSpace(spaceId, actor) {
|
|
221
|
+
const space = await this.content.getSpace(spaceId, this.system(actor.orgId));
|
|
116
222
|
if (!space) return false;
|
|
117
|
-
return this.canOpen(
|
|
223
|
+
return this.canOpen(space, actor);
|
|
118
224
|
}
|
|
119
|
-
async canOpen(
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
return opensSpace(facts.role, facts.member, space.accessRule, facts.activeInSpace);
|
|
225
|
+
async canOpen(space, actor) {
|
|
226
|
+
const standing = await this.standingOf(actor, space.communityId, space.id);
|
|
227
|
+
return opensSpace(standing.member, standing.everySpace, space.accessRule, standing.activeInSpace);
|
|
123
228
|
}
|
|
124
|
-
async spaceStandings(
|
|
125
|
-
|
|
229
|
+
async spaceStandings(communityId, actor) {
|
|
230
|
+
if ((await this.standingOf(actor, communityId, null)).everySpace) return {};
|
|
231
|
+
const rows = await this.repo.listSpaceMembersFor(actor.orgId, actor.id, communityId);
|
|
126
232
|
return Object.fromEntries(rows.map((row) => [row.spaceId, row.status]));
|
|
127
233
|
}
|
|
128
|
-
async openSpacesFor(
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
234
|
+
async openSpacesFor(communityId, actor) {
|
|
235
|
+
const orgId = actor.orgId;
|
|
236
|
+
const standing = await this.standingOf(actor, communityId, null);
|
|
237
|
+
if (!standing.member) return [];
|
|
238
|
+
const [spaces, activeIds] = await Promise.all([this.content.listSpaces(communityId, this.system(orgId)), standing.everySpace ? Promise.resolve([]) : this.repo.activeSpaceIds(orgId, actor.id, communityId)]);
|
|
132
239
|
const active = new Set(activeIds);
|
|
133
|
-
return spaces.filter((space) => opensSpace(
|
|
240
|
+
return spaces.filter((space) => opensSpace(true, standing.everySpace, space.accessRule, active.has(space.id)));
|
|
134
241
|
}
|
|
135
|
-
async openSpaceIds(
|
|
136
|
-
return (await this.openSpacesFor(
|
|
242
|
+
async openSpaceIds(communityId, actor) {
|
|
243
|
+
return (await this.openSpacesFor(communityId, actor)).map((space) => space.id);
|
|
137
244
|
}
|
|
138
245
|
async communityMembersAmong(orgId, communityId, orgUserIds) {
|
|
139
246
|
if (orgUserIds.length === 0) return [];
|
|
140
|
-
|
|
247
|
+
const [facts, team] = await Promise.all([this.repo.accessAmong(orgId, communityId, null, orgUserIds), this.teamOf(orgId, communityId)]);
|
|
248
|
+
return facts.filter((fact) => standingFrom(fact, team).member).map((fact) => fact.orgUserId);
|
|
141
249
|
}
|
|
142
250
|
async spaceOpenersAmong(orgId, spaceId, orgUserIds) {
|
|
143
251
|
if (orgUserIds.length === 0) return [];
|
|
144
|
-
const space = await this.content.getSpace(
|
|
252
|
+
const space = await this.content.getSpace(spaceId, this.system(orgId));
|
|
145
253
|
if (!space) return [];
|
|
146
|
-
|
|
254
|
+
const [facts, team] = await Promise.all([this.repo.accessAmong(orgId, space.communityId, space.id, orgUserIds), this.teamOf(orgId, space.communityId)]);
|
|
255
|
+
return facts.filter((fact) => {
|
|
256
|
+
const standing = standingFrom(fact, team);
|
|
257
|
+
return opensSpace(standing.member, standing.everySpace, space.accessRule, standing.activeInSpace);
|
|
258
|
+
}).map((fact) => fact.orgUserId);
|
|
147
259
|
}
|
|
148
260
|
async spaceOpeners(orgId, spaceId) {
|
|
149
|
-
const space = await this.content.getSpace(
|
|
261
|
+
const space = await this.content.getSpace(spaceId, this.system(orgId));
|
|
150
262
|
if (!space) return [];
|
|
151
|
-
const members = await this.
|
|
263
|
+
const members = await this.communityMemberIds(orgId, space.communityId);
|
|
152
264
|
return this.spaceOpenersAmong(orgId, spaceId, members);
|
|
153
265
|
}
|
|
154
266
|
communitiesOpenedBy(orgId, grant) {
|
|
155
267
|
return this.repo.communitiesOpenedBy(orgId, grant);
|
|
156
268
|
}
|
|
157
|
-
communityModeratorIds(orgId, communityId) {
|
|
158
|
-
|
|
269
|
+
async communityModeratorIds(orgId, communityId) {
|
|
270
|
+
const moderators = await this.permissions.holders(orgId, "communities:moderate", communityId);
|
|
271
|
+
return this.communityMembersAmong(orgId, communityId, moderators);
|
|
159
272
|
}
|
|
160
273
|
communityIdsOpenedFor(orgId, orgUserId) {
|
|
161
274
|
return this.repo.communityIdsOpenedFor(orgId, orgUserId);
|
|
162
275
|
}
|
|
163
|
-
|
|
164
|
-
|
|
276
|
+
openCommunityIds(actor) {
|
|
277
|
+
const readable = actor.permissions.where("communities:read");
|
|
278
|
+
return this.repo.openCommunityIds(actor.orgId, actor.id, {
|
|
279
|
+
all: readable.org,
|
|
280
|
+
ids: readable.content
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
async communityMemberIds(orgId, communityId) {
|
|
284
|
+
const readers = await this.permissions.holders(orgId, "communities:read", communityId);
|
|
285
|
+
return this.repo.communityMemberIds(orgId, communityId, readers);
|
|
286
|
+
}
|
|
287
|
+
grantedOrgUserIds(orgId, contentId) {
|
|
288
|
+
return this.repo.grantedOrgUserIds(orgId, contentId);
|
|
289
|
+
}
|
|
290
|
+
async countActiveSpaceMembers(orgId, spaceId) {
|
|
291
|
+
const space = await this.content.getSpace(spaceId, this.system(orgId));
|
|
292
|
+
if (!space) return 0;
|
|
293
|
+
const excluding = await this.everySpaceOpeners(orgId, space.communityId);
|
|
294
|
+
return this.repo.countActiveSpaceMembers(orgId, spaceId, excluding);
|
|
295
|
+
}
|
|
296
|
+
async everySpaceOpeners(orgId, communityId) {
|
|
297
|
+
const team = await this.teamOf(orgId, communityId);
|
|
298
|
+
const moderators = [...team.moderators].filter((id) => !team.readers.has(id));
|
|
299
|
+
const granted = moderators.length === 0 ? [] : (await this.repo.accessAmong(orgId, communityId, null, moderators)).filter((fact) => fact.granted).map((fact) => fact.orgUserId);
|
|
300
|
+
return [...team.readers, ...granted];
|
|
165
301
|
}
|
|
166
|
-
async requestJoin(
|
|
167
|
-
const
|
|
302
|
+
async requestJoin(communityId, spaceId, actor) {
|
|
303
|
+
const orgId = actor.orgId;
|
|
304
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
168
305
|
this.assertRule(space.accessRule, "by_request");
|
|
169
|
-
|
|
170
|
-
await this.
|
|
306
|
+
await this.assertHolder(actor, space.communityId);
|
|
307
|
+
await this.assertMember(actor, actor.id, space.communityId);
|
|
171
308
|
const created = await this.createMember(orgId, actor, space, actor.id, "requested", "request");
|
|
172
309
|
this.logger.info("space member created", {
|
|
173
310
|
orgId,
|
|
@@ -178,11 +315,12 @@ var EntitlementsServiceImpl = class {
|
|
|
178
315
|
});
|
|
179
316
|
return created;
|
|
180
317
|
}
|
|
181
|
-
async invite(
|
|
182
|
-
const
|
|
318
|
+
async invite(communityId, spaceId, orgUserId, actor) {
|
|
319
|
+
const orgId = actor.orgId;
|
|
320
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
321
|
+
await this.assertCanManage(actor, space.communityId);
|
|
183
322
|
this.assertRule(space.accessRule, "by_invite");
|
|
184
|
-
await this.
|
|
185
|
-
await this.assertHolds(orgId, actor, orgUserId, space.communityId);
|
|
323
|
+
await this.assertHolds(actor, orgUserId, space.communityId);
|
|
186
324
|
const created = await this.createMember(orgId, actor, space, orgUserId, "invited", "invite");
|
|
187
325
|
this.logger.info("space member created", {
|
|
188
326
|
orgId,
|
|
@@ -193,11 +331,13 @@ var EntitlementsServiceImpl = class {
|
|
|
193
331
|
});
|
|
194
332
|
return created;
|
|
195
333
|
}
|
|
196
|
-
async addSpaceMember(
|
|
197
|
-
const
|
|
334
|
+
async addSpaceMember(communityId, spaceId, orgUserId, actor) {
|
|
335
|
+
const orgId = actor.orgId;
|
|
336
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
337
|
+
await this.assertCanManage(actor, space.communityId);
|
|
198
338
|
this.assertHasMembers(space.accessRule);
|
|
199
|
-
await this.
|
|
200
|
-
await this.
|
|
339
|
+
await this.assertHolds(actor, orgUserId, space.communityId);
|
|
340
|
+
const excluding = await this.everySpaceOpeners(orgId, space.communityId);
|
|
201
341
|
const created = await this.uow.run(async (scope) => {
|
|
202
342
|
const locked = await this.lockSpace(scope.spaces, orgId, spaceId);
|
|
203
343
|
this.assertHasMembers(locked.accessRule);
|
|
@@ -207,7 +347,10 @@ var EntitlementsServiceImpl = class {
|
|
|
207
347
|
orgUserId,
|
|
208
348
|
status: "active",
|
|
209
349
|
source: "manual"
|
|
210
|
-
},
|
|
350
|
+
}, {
|
|
351
|
+
space: locked,
|
|
352
|
+
excluding
|
|
353
|
+
});
|
|
211
354
|
});
|
|
212
355
|
this.logger.info("space member created", {
|
|
213
356
|
orgId,
|
|
@@ -218,41 +361,46 @@ var EntitlementsServiceImpl = class {
|
|
|
218
361
|
});
|
|
219
362
|
return created;
|
|
220
363
|
}
|
|
221
|
-
async accept(
|
|
222
|
-
|
|
223
|
-
const space = await this.requireVisibleSpaceIn(
|
|
364
|
+
async accept(communityId, spaceId, actor) {
|
|
365
|
+
const orgId = actor.orgId;
|
|
366
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
224
367
|
this.assertRule(space.accessRule, "by_invite");
|
|
225
|
-
await this.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
368
|
+
await this.assertHolder(actor, space.communityId);
|
|
369
|
+
await this.assertMember(actor, actor.id, space.communityId);
|
|
370
|
+
const excluding = await this.everySpaceOpeners(orgId, space.communityId);
|
|
371
|
+
return this.activate(orgId, actor, spaceId, actor.id, "by_invite", "invited", excluding, () => ownRowMissing("invited"));
|
|
372
|
+
}
|
|
373
|
+
async approve(communityId, spaceId, orgUserId, actor) {
|
|
374
|
+
const orgId = actor.orgId;
|
|
375
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
376
|
+
await this.assertCanManage(actor, space.communityId);
|
|
230
377
|
this.assertRule(space.accessRule, "by_request");
|
|
231
|
-
await this.
|
|
232
|
-
await this.
|
|
233
|
-
return this.activate(orgId, actor, spaceId, orgUserId, "by_request", "requested", () => new NotFoundError("Space member", orgUserId));
|
|
234
|
-
}
|
|
235
|
-
async decline(
|
|
236
|
-
const space = await this.requireVisibleSpaceIn(
|
|
237
|
-
await this.assertCanManage(
|
|
238
|
-
return this.deleteMember(orgId, actor, spaceId, orgUserId, "requested", () => new NotFoundError("Space member", orgUserId));
|
|
239
|
-
}
|
|
240
|
-
async refuse(
|
|
241
|
-
const space = await this.requireVisibleSpaceIn(
|
|
242
|
-
this.assertHolder(actor);
|
|
243
|
-
await this.
|
|
244
|
-
return this.deleteMember(orgId, actor, spaceId, actor.id, "invited", () => ownRowMissing("invited"));
|
|
245
|
-
}
|
|
246
|
-
async leave(
|
|
247
|
-
const space = await this.requireVisibleSpaceIn(
|
|
248
|
-
this.assertHolder(actor);
|
|
249
|
-
await this.
|
|
250
|
-
return this.deleteMember(orgId, actor, spaceId, actor.id, "active", () => ownRowMissing("active"));
|
|
251
|
-
}
|
|
252
|
-
async remove(
|
|
253
|
-
const
|
|
378
|
+
await this.assertHolds(actor, orgUserId, space.communityId);
|
|
379
|
+
const excluding = await this.everySpaceOpeners(orgId, space.communityId);
|
|
380
|
+
return this.activate(orgId, actor, spaceId, orgUserId, "by_request", "requested", excluding, () => new NotFoundError("Space member", orgUserId));
|
|
381
|
+
}
|
|
382
|
+
async decline(communityId, spaceId, orgUserId, actor) {
|
|
383
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
384
|
+
await this.assertCanManage(actor, space.communityId);
|
|
385
|
+
return this.deleteMember(actor.orgId, actor, spaceId, orgUserId, "requested", () => new NotFoundError("Space member", orgUserId));
|
|
386
|
+
}
|
|
387
|
+
async refuse(communityId, spaceId, actor) {
|
|
388
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
389
|
+
await this.assertHolder(actor, space.communityId);
|
|
390
|
+
await this.assertMember(actor, actor.id, space.communityId);
|
|
391
|
+
return this.deleteMember(actor.orgId, actor, spaceId, actor.id, "invited", () => ownRowMissing("invited"));
|
|
392
|
+
}
|
|
393
|
+
async leave(communityId, spaceId, actor) {
|
|
394
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
395
|
+
await this.assertHolder(actor, space.communityId);
|
|
396
|
+
await this.assertMember(actor, actor.id, space.communityId);
|
|
397
|
+
return this.deleteMember(actor.orgId, actor, spaceId, actor.id, "active", () => ownRowMissing("active"));
|
|
398
|
+
}
|
|
399
|
+
async remove(communityId, spaceId, orgUserId, actor) {
|
|
400
|
+
const orgId = actor.orgId;
|
|
401
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
402
|
+
await this.assertCanManage(actor, space.communityId);
|
|
254
403
|
this.assertHasMembers(space.accessRule);
|
|
255
|
-
await this.assertCanManage(orgId, actor, space.communityId);
|
|
256
404
|
const { updated, previousStatus } = await this.uow.run(async ({ entitlements, spaces, outbox }) => {
|
|
257
405
|
const locked = await this.lockSpace(spaces, orgId, spaceId);
|
|
258
406
|
this.assertHasMembers(locked.accessRule);
|
|
@@ -273,7 +421,7 @@ var EntitlementsServiceImpl = class {
|
|
|
273
421
|
orgId,
|
|
274
422
|
data: next,
|
|
275
423
|
metadata: {
|
|
276
|
-
|
|
424
|
+
actorId: actor.id,
|
|
277
425
|
previousStatus: row.status
|
|
278
426
|
}
|
|
279
427
|
})]);
|
|
@@ -291,18 +439,32 @@ var EntitlementsServiceImpl = class {
|
|
|
291
439
|
});
|
|
292
440
|
return updated;
|
|
293
441
|
}
|
|
294
|
-
async listSpaceMembers(
|
|
295
|
-
const
|
|
296
|
-
|
|
442
|
+
async listSpaceMembers(communityId, spaceId, query, actor) {
|
|
443
|
+
const orgId = actor.orgId;
|
|
444
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
445
|
+
if (!await this.canOpen(space, actor)) throw new NotFoundError("Space", spaceId);
|
|
446
|
+
const excluding = await this.everySpaceOpeners(orgId, space.communityId);
|
|
297
447
|
return this.repo.listSpaceMembers(orgId, spaceId, {
|
|
298
448
|
...query,
|
|
299
|
-
|
|
449
|
+
excluding,
|
|
450
|
+
...actor.permissions.has("communities:moderate", space.communityId) ? {} : { statuses: ["active"] }
|
|
300
451
|
});
|
|
301
452
|
}
|
|
302
|
-
|
|
303
|
-
|
|
453
|
+
async expireDueGrants(now) {
|
|
454
|
+
const orgIds = await this.repo.orgIdsWithDueNotices(now, new Date(now.getTime() + ENDING_SOON_MS));
|
|
455
|
+
let failed = 0;
|
|
456
|
+
for (const orgId of orgIds) try {
|
|
457
|
+
await this.expireDueIn(orgId, now);
|
|
458
|
+
} catch (err) {
|
|
459
|
+
failed += 1;
|
|
460
|
+
this.logger.error("due grants not expired", {
|
|
461
|
+
orgId,
|
|
462
|
+
err
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (failed > 0) throw new Error(`due grants of ${failed} orgs could not be expired`);
|
|
304
466
|
}
|
|
305
|
-
async
|
|
467
|
+
async expireDueIn(orgId, now) {
|
|
306
468
|
const until = new Date(now.getTime() + ENDING_SOON_MS);
|
|
307
469
|
let appended = 0;
|
|
308
470
|
let afterId;
|
|
@@ -372,8 +534,34 @@ var EntitlementsServiceImpl = class {
|
|
|
372
534
|
events: appended
|
|
373
535
|
});
|
|
374
536
|
}
|
|
537
|
+
system(orgId) {
|
|
538
|
+
return this.permissions.systemActor(orgId);
|
|
539
|
+
}
|
|
540
|
+
async teamOf(orgId, communityId) {
|
|
541
|
+
const [readers, moderators] = await Promise.all([this.permissions.holders(orgId, "communities:read", communityId), this.permissions.holders(orgId, "communities:moderate", communityId)]);
|
|
542
|
+
return {
|
|
543
|
+
readers: new Set(readers),
|
|
544
|
+
moderators: new Set(moderators)
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
async standingOf(actor, communityId, spaceId) {
|
|
548
|
+
const reads = actor.permissions.has("communities:read", communityId);
|
|
549
|
+
const moderates = actor.permissions.has("communities:moderate", communityId);
|
|
550
|
+
if (reads) return {
|
|
551
|
+
member: true,
|
|
552
|
+
everySpace: true,
|
|
553
|
+
activeInSpace: false
|
|
554
|
+
};
|
|
555
|
+
const facts = await this.accessOf(actor.orgId, actor.id, communityId, spaceId);
|
|
556
|
+
const member = facts?.granted === true;
|
|
557
|
+
return {
|
|
558
|
+
member,
|
|
559
|
+
everySpace: member && moderates,
|
|
560
|
+
activeInSpace: facts?.activeInSpace === true
|
|
561
|
+
};
|
|
562
|
+
}
|
|
375
563
|
async requireSpaceIn(orgId, communityId, spaceId) {
|
|
376
|
-
const space = await this.content.getSpace(
|
|
564
|
+
const space = await this.content.getSpace(spaceId, this.system(orgId));
|
|
377
565
|
if (!space || space.communityId !== communityId) {
|
|
378
566
|
this.logger.warn("space members rejected — space not found", {
|
|
379
567
|
orgId,
|
|
@@ -384,11 +572,13 @@ var EntitlementsServiceImpl = class {
|
|
|
384
572
|
}
|
|
385
573
|
return space;
|
|
386
574
|
}
|
|
387
|
-
async requireVisibleSpaceIn(
|
|
575
|
+
async requireVisibleSpaceIn(actor, communityId, spaceId) {
|
|
576
|
+
const orgId = actor.orgId;
|
|
388
577
|
const space = await this.requireSpaceIn(orgId, communityId, spaceId);
|
|
389
|
-
if (seesSpace(
|
|
578
|
+
if (seesSpace(false, space.accessRule, void 0)) return space;
|
|
579
|
+
if (await this.opensEverySpace(actor, communityId)) return space;
|
|
390
580
|
const row = await this.repo.findSpaceMember(orgId, spaceId, actor.id);
|
|
391
|
-
if (!seesSpace(
|
|
581
|
+
if (!seesSpace(false, space.accessRule, row?.status)) throw new NotFoundError("Space", spaceId);
|
|
392
582
|
return space;
|
|
393
583
|
}
|
|
394
584
|
assertHasMembers(accessRule) {
|
|
@@ -398,21 +588,39 @@ var EntitlementsServiceImpl = class {
|
|
|
398
588
|
this.assertHasMembers(accessRule);
|
|
399
589
|
if (accessRule !== expected) throw new ConflictError(accessRule === "by_invite" ? "This space is by invitation" : "This space is by request");
|
|
400
590
|
}
|
|
401
|
-
assertHolder(actor) {
|
|
402
|
-
if (
|
|
591
|
+
async assertHolder(actor, communityId) {
|
|
592
|
+
if (await this.opensEverySpace(actor, communityId)) {
|
|
593
|
+
this.logger.warn("space place refused — opens every space", {
|
|
594
|
+
orgId: actor.orgId,
|
|
595
|
+
communityId,
|
|
596
|
+
actorId: actor.id
|
|
597
|
+
});
|
|
598
|
+
throw new ForbiddenError("You open every space in this community and hold no place in them");
|
|
599
|
+
}
|
|
403
600
|
}
|
|
404
|
-
async
|
|
405
|
-
if (!await this.
|
|
601
|
+
async assertMember(actor, orgUserId, communityId) {
|
|
602
|
+
if (!(orgUserId === actor.id ? await this.isCommunityMember(communityId, actor) : (await this.communityMembersAmong(actor.orgId, communityId, [orgUserId])).length > 0)) throw new ForbiddenError(orgUserId === actor.id ? "You are not a member of this community" : "That person is not a member of this community");
|
|
406
603
|
}
|
|
407
|
-
async assertHolds(
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
604
|
+
async assertHolds(actor, orgUserId, communityId) {
|
|
605
|
+
if ((await this.everySpaceOpeners(actor.orgId, communityId)).includes(orgUserId)) {
|
|
606
|
+
this.logger.warn("space place refused — person opens every space", {
|
|
607
|
+
orgId: actor.orgId,
|
|
608
|
+
communityId,
|
|
609
|
+
orgUserId,
|
|
610
|
+
actorId: actor.id
|
|
611
|
+
});
|
|
612
|
+
throw new ForbiddenError("That person opens every space in this community and holds no place in them");
|
|
613
|
+
}
|
|
614
|
+
await this.assertMember(actor, orgUserId, communityId);
|
|
411
615
|
}
|
|
412
|
-
async assertCanManage(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
616
|
+
async assertCanManage(actor, communityId) {
|
|
617
|
+
if (actor.permissions.has("communities:moderate", communityId) && await this.isCommunityMember(communityId, actor)) return;
|
|
618
|
+
this.logger.warn("space members refused — cannot manage", {
|
|
619
|
+
orgId: actor.orgId,
|
|
620
|
+
communityId,
|
|
621
|
+
actorId: actor.id
|
|
622
|
+
});
|
|
623
|
+
throw new ForbiddenError("You need communities:moderate on this community, as its member");
|
|
416
624
|
}
|
|
417
625
|
async accessOf(orgId, orgUserId, communityId, spaceId) {
|
|
418
626
|
const [facts] = await this.repo.accessAmong(orgId, communityId, spaceId, [orgUserId]);
|
|
@@ -430,17 +638,17 @@ var EntitlementsServiceImpl = class {
|
|
|
430
638
|
async insertMember(entitlements, outbox, orgId, actor, input, room) {
|
|
431
639
|
const existing = await entitlements.lockSpaceMember(orgId, input.spaceId, input.orgUserId);
|
|
432
640
|
if (existing && existing.status !== "revoked") throw new ConflictError(input.orgUserId === actor.id ? "You already have a place in this space" : "That person already has a place in this space");
|
|
433
|
-
if (room) await this.assertRoom(entitlements, orgId, room);
|
|
641
|
+
if (room) await this.assertRoom(entitlements, orgId, room.space, room.excluding);
|
|
434
642
|
if (existing) await entitlements.deleteSpaceMember(orgId, existing.id);
|
|
435
643
|
const created = await entitlements.insertSpaceMember(orgId, input);
|
|
436
644
|
await outbox.append([entitlementEvents.spaceMemberCreated.make({
|
|
437
645
|
orgId,
|
|
438
646
|
data: created,
|
|
439
|
-
metadata: {
|
|
647
|
+
metadata: { actorId: actor.id }
|
|
440
648
|
})]);
|
|
441
649
|
return created;
|
|
442
650
|
}
|
|
443
|
-
async activate(orgId, actor, spaceId, orgUserId, rule, from, missing) {
|
|
651
|
+
async activate(orgId, actor, spaceId, orgUserId, rule, from, excluding, missing) {
|
|
444
652
|
const updated = await this.uow.run(async ({ entitlements, spaces, outbox }) => {
|
|
445
653
|
const locked = await this.lockSpace(spaces, orgId, spaceId);
|
|
446
654
|
this.assertRule(locked.accessRule, rule);
|
|
@@ -455,14 +663,14 @@ var EntitlementsServiceImpl = class {
|
|
|
455
663
|
});
|
|
456
664
|
throw missing();
|
|
457
665
|
}
|
|
458
|
-
await this.assertRoom(entitlements, orgId, locked);
|
|
666
|
+
await this.assertRoom(entitlements, orgId, locked, excluding);
|
|
459
667
|
const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, "active");
|
|
460
668
|
if (!next) throw missing();
|
|
461
669
|
await outbox.append([entitlementEvents.spaceMemberUpdated.make({
|
|
462
670
|
orgId,
|
|
463
671
|
data: next,
|
|
464
672
|
metadata: {
|
|
465
|
-
|
|
673
|
+
actorId: actor.id,
|
|
466
674
|
previousStatus: from
|
|
467
675
|
}
|
|
468
676
|
})]);
|
|
@@ -494,7 +702,7 @@ var EntitlementsServiceImpl = class {
|
|
|
494
702
|
orgId,
|
|
495
703
|
data: current,
|
|
496
704
|
metadata: {
|
|
497
|
-
|
|
705
|
+
actorId: actor.id,
|
|
498
706
|
previousStatus: from
|
|
499
707
|
}
|
|
500
708
|
})]);
|
|
@@ -514,17 +722,36 @@ var EntitlementsServiceImpl = class {
|
|
|
514
722
|
if (!locked) throw new NotFoundError("Space", spaceId);
|
|
515
723
|
return locked;
|
|
516
724
|
}
|
|
517
|
-
async assertRoom(entitlements, orgId, space) {
|
|
725
|
+
async assertRoom(entitlements, orgId, space, excluding) {
|
|
518
726
|
if (space.capacity === null) return;
|
|
519
|
-
if (await entitlements.countActiveSpaceMembers(orgId, space.id) >= space.capacity) throw new ConflictError("This space is full");
|
|
727
|
+
if (await entitlements.countActiveSpaceMembers(orgId, space.id, excluding) >= space.capacity) throw new ConflictError("This space is full");
|
|
520
728
|
}
|
|
521
729
|
};
|
|
730
|
+
function alreadyRegistered() {
|
|
731
|
+
return new ConflictError("You are already going to this event", "already_registered");
|
|
732
|
+
}
|
|
733
|
+
function activeNow(grant, now) {
|
|
734
|
+
return grant.status === "active" && (grant.expiresAt === null || grant.expiresAt.getTime() > now.getTime());
|
|
735
|
+
}
|
|
736
|
+
function assertOpenForAttendees(event, now) {
|
|
737
|
+
if (event.status !== "published") throw new ConflictError("This event is not published", "not_published");
|
|
738
|
+
if (event.endsAt.getTime() <= now.getTime()) throw new ConflictError("This event has ended", "ended");
|
|
739
|
+
}
|
|
522
740
|
function alreadyPlaced(actor, orgUserId) {
|
|
523
741
|
return new ConflictError(orgUserId === actor.id ? "You already have a place in this space" : "That person already has a place in this space");
|
|
524
742
|
}
|
|
525
743
|
function ownRowMissing(status) {
|
|
526
744
|
return new ConflictError(status === "invited" ? "You do not have an invitation to this space" : "You are not a member of this space");
|
|
527
745
|
}
|
|
746
|
+
function standingFrom(fact, team) {
|
|
747
|
+
const reads = team.readers.has(fact.orgUserId);
|
|
748
|
+
const member = reads || fact.granted;
|
|
749
|
+
return {
|
|
750
|
+
member,
|
|
751
|
+
everySpace: reads || member && team.moderators.has(fact.orgUserId),
|
|
752
|
+
activeInSpace: fact.activeInSpace
|
|
753
|
+
};
|
|
754
|
+
}
|
|
528
755
|
//#endregion
|
|
529
756
|
export { EntitlementsServiceImpl };
|
|
530
757
|
|