@headless-lms/core 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/events.d.ts +15 -9
- package/dist/assets/file-urls.d.ts +14 -7
- package/dist/assets/file-urls.js +15 -4
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +4 -4
- package/dist/assets/index.js +3 -3
- package/dist/assets/ports.d.ts +34 -15
- package/dist/assets/service.d.ts +30 -14
- package/dist/assets/service.js +233 -54
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.js +68 -3
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +16 -16
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +34 -4
- package/dist/automations/community-mail.js +32 -4
- 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/index.d.ts +2 -1
- package/dist/automations/index.js +2 -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.js +1 -1
- package/dist/automations/notifications/debounce.js.map +1 -1
- package/dist/automations/notifications/defaults.js +33 -3
- package/dist/automations/notifications/defaults.js.map +1 -1
- package/dist/automations/notifications/processor.js +1 -1
- package/dist/automations/notifications/processor.js.map +1 -1
- package/dist/automations/notifications/rules/content.js +60 -3
- package/dist/automations/notifications/rules/content.js.map +1 -1
- package/dist/automations/notifications/rules/discussion.js +29 -29
- package/dist/automations/notifications/rules/discussion.js.map +1 -1
- package/dist/automations/notifications/rules/entitlements.js +36 -9
- package/dist/automations/notifications/rules/entitlements.js.map +1 -1
- 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/organizations.js +17 -10
- package/dist/automations/notifications/rules/organizations.js.map +1 -1
- package/dist/automations/notifications/rules/recipients.js +0 -1
- package/dist/automations/notifications/rules/recipients.js.map +1 -1
- package/dist/automations/notifications/service.d.ts +8 -6
- package/dist/automations/notifications/service.js +12 -12
- package/dist/automations/notifications/service.js.map +1 -1
- package/dist/automations/notifications/types.js +94 -7
- package/dist/automations/notifications/types.js.map +1 -1
- package/dist/automations/ports.d.ts +30 -31
- package/dist/automations/service.d.ts +20 -17
- package/dist/automations/service.js +90 -61
- 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 +465 -88
- package/dist/content/events.js +34 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +6 -4
- package/dist/content/index.js +3 -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 +5 -4
- package/dist/content/notification-categories.js +15 -6
- package/dist/content/notification-categories.js.map +1 -1
- package/dist/content/ports.d.ts +113 -68
- package/dist/content/service.d.ts +103 -67
- package/dist/content/service.js +739 -152
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +6 -3
- package/dist/content/titles.js +37 -24
- package/dist/content/titles.js.map +1 -1
- 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 +56 -37
- package/dist/discussion/chats.js +392 -160
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/events.d.ts +85 -47
- package/dist/discussion/events.js +41 -3
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +8 -5
- package/dist/discussion/index.js +5 -3
- 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.js +2 -2
- package/dist/discussion/mentions.js.map +1 -1
- package/dist/discussion/ports.d.ts +182 -100
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +45 -39
- package/dist/discussion/posts.js +266 -152
- 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 +37 -31
- package/dist/discussion/service.js +250 -186
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +8 -8
- package/dist/discussion/subscriptions.js +28 -12
- package/dist/discussion/subscriptions.js.map +1 -1
- package/dist/discussion/titles.d.ts +4 -2
- package/dist/discussion/titles.js +27 -13
- package/dist/discussion/titles.js.map +1 -1
- package/dist/entitlements/events.d.ts +58 -58
- package/dist/entitlements/index.d.ts +4 -4
- package/dist/entitlements/index.js +2 -2
- package/dist/entitlements/model.d.ts +2 -2
- package/dist/entitlements/ports.d.ts +65 -39
- 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 +40 -28
- package/dist/entitlements/service.js +369 -162
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +3 -3
- package/dist/identity/devices.js +6 -4
- package/dist/identity/devices.js.map +1 -1
- package/dist/identity/index.d.ts +2 -2
- package/dist/identity/index.js +2 -2
- package/dist/identity/notification-settings.d.ts +1 -1
- package/dist/identity/notification-settings.js +2 -1
- package/dist/identity/notification-settings.js.map +1 -1
- package/dist/identity/ports.d.ts +7 -7
- package/dist/identity/service.d.ts +8 -7
- package/dist/identity/service.js +16 -12
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/events.d.ts +3 -3
- package/dist/integrations/ports.d.ts +9 -8
- package/dist/integrations/service.d.ts +9 -8
- package/dist/integrations/service.js +31 -14
- 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 +47 -0
- package/dist/organizations/effective-permissions.js +118 -0
- package/dist/organizations/effective-permissions.js.map +1 -0
- package/dist/organizations/events.d.ts +326 -181
- package/dist/organizations/events.js +90 -18
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +14 -10
- package/dist/organizations/index.js +7 -3
- package/dist/organizations/members.d.ts +66 -8
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/model.d.ts +2 -2
- 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 +6 -4
- package/dist/organizations/notification-categories.js +25 -10
- package/dist/organizations/notification-categories.js.map +1 -1
- 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 +102 -35
- package/dist/organizations/service.js +1254 -327
- 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/events.d.ts +14 -14
- 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 +21 -6
- 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 +29 -5
- package/dist/reporting/member/ports.d.ts +49 -29
- package/dist/reporting/member/service.d.ts +57 -29
- package/dist/reporting/member/service.js +345 -114
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +23 -3
- package/dist/shared/errors.d.ts +2 -1
- package/dist/shared/errors.js +3 -1
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +4 -0
- package/dist/shared/id.js +4 -0
- 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/ports.d.ts +25 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +4 -5
- package/dist/types/email-templates.d.ts +22 -3
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +13 -16
- package/dist/types/index.d.ts +9 -9
- package/dist/types/index.js +2 -1
- package/dist/types/notifications.d.ts +4 -4
- 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/content.d.ts +599 -13
- package/dist/types/schemas/content.js +219 -8
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +280 -57
- package/dist/types/schemas/discussion.js +77 -3
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +21 -8
- package/dist/types/schemas/entitlements.js +11 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/index.d.ts +5 -5
- package/dist/types/schemas/index.js +5 -5
- package/dist/types/schemas/notifications.d.ts +11 -10
- package/dist/types/schemas/notifications.js +3 -2
- package/dist/types/schemas/notifications.js.map +1 -1
- package/dist/types/schemas/organizations.d.ts +99 -60
- package/dist/types/schemas/organizations.js +80 -27
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +3 -4
- package/dist/types/schemas/progress.js +0 -1
- package/dist/types/schemas/progress.js.map +1 -1
- package/package.json +1 -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
1
|
import { ConflictError, ForbiddenError, NotFoundError } from "../shared/errors.js";
|
|
2
2
|
import { noopLogger } from "../shared/logger.js";
|
|
3
|
-
import { canModerate, isStaffRole } from "../organizations/roles.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,75 +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
|
-
openCommunityIds(
|
|
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);
|
|
165
286
|
}
|
|
166
|
-
|
|
167
|
-
return this.repo.
|
|
287
|
+
grantedOrgUserIds(orgId, contentId) {
|
|
288
|
+
return this.repo.grantedOrgUserIds(orgId, contentId);
|
|
168
289
|
}
|
|
169
|
-
|
|
170
|
-
|
|
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);
|
|
171
295
|
}
|
|
172
|
-
|
|
173
|
-
|
|
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];
|
|
174
301
|
}
|
|
175
|
-
async requestJoin(
|
|
176
|
-
const
|
|
302
|
+
async requestJoin(communityId, spaceId, actor) {
|
|
303
|
+
const orgId = actor.orgId;
|
|
304
|
+
const space = await this.requireVisibleSpaceIn(actor, communityId, spaceId);
|
|
177
305
|
this.assertRule(space.accessRule, "by_request");
|
|
178
|
-
|
|
179
|
-
await this.
|
|
306
|
+
await this.assertHolder(actor, space.communityId);
|
|
307
|
+
await this.assertMember(actor, actor.id, space.communityId);
|
|
180
308
|
const created = await this.createMember(orgId, actor, space, actor.id, "requested", "request");
|
|
181
309
|
this.logger.info("space member created", {
|
|
182
310
|
orgId,
|
|
@@ -187,11 +315,12 @@ var EntitlementsServiceImpl = class {
|
|
|
187
315
|
});
|
|
188
316
|
return created;
|
|
189
317
|
}
|
|
190
|
-
async invite(
|
|
191
|
-
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);
|
|
192
322
|
this.assertRule(space.accessRule, "by_invite");
|
|
193
|
-
await this.
|
|
194
|
-
await this.assertHolds(orgId, actor, orgUserId, space.communityId);
|
|
323
|
+
await this.assertHolds(actor, orgUserId, space.communityId);
|
|
195
324
|
const created = await this.createMember(orgId, actor, space, orgUserId, "invited", "invite");
|
|
196
325
|
this.logger.info("space member created", {
|
|
197
326
|
orgId,
|
|
@@ -202,11 +331,13 @@ var EntitlementsServiceImpl = class {
|
|
|
202
331
|
});
|
|
203
332
|
return created;
|
|
204
333
|
}
|
|
205
|
-
async addSpaceMember(
|
|
206
|
-
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);
|
|
207
338
|
this.assertHasMembers(space.accessRule);
|
|
208
|
-
await this.
|
|
209
|
-
await this.
|
|
339
|
+
await this.assertHolds(actor, orgUserId, space.communityId);
|
|
340
|
+
const excluding = await this.everySpaceOpeners(orgId, space.communityId);
|
|
210
341
|
const created = await this.uow.run(async (scope) => {
|
|
211
342
|
const locked = await this.lockSpace(scope.spaces, orgId, spaceId);
|
|
212
343
|
this.assertHasMembers(locked.accessRule);
|
|
@@ -216,7 +347,10 @@ var EntitlementsServiceImpl = class {
|
|
|
216
347
|
orgUserId,
|
|
217
348
|
status: "active",
|
|
218
349
|
source: "manual"
|
|
219
|
-
},
|
|
350
|
+
}, {
|
|
351
|
+
space: locked,
|
|
352
|
+
excluding
|
|
353
|
+
});
|
|
220
354
|
});
|
|
221
355
|
this.logger.info("space member created", {
|
|
222
356
|
orgId,
|
|
@@ -227,41 +361,46 @@ var EntitlementsServiceImpl = class {
|
|
|
227
361
|
});
|
|
228
362
|
return created;
|
|
229
363
|
}
|
|
230
|
-
async accept(
|
|
231
|
-
|
|
232
|
-
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);
|
|
233
367
|
this.assertRule(space.accessRule, "by_invite");
|
|
234
|
-
await this.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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);
|
|
239
377
|
this.assertRule(space.accessRule, "by_request");
|
|
240
|
-
await this.
|
|
241
|
-
await this.
|
|
242
|
-
return this.activate(orgId, actor, spaceId, orgUserId, "by_request", "requested", () => new NotFoundError("Space member", orgUserId));
|
|
243
|
-
}
|
|
244
|
-
async decline(
|
|
245
|
-
const space = await this.requireVisibleSpaceIn(
|
|
246
|
-
await this.assertCanManage(
|
|
247
|
-
return this.deleteMember(orgId, actor, spaceId, orgUserId, "requested", () => new NotFoundError("Space member", orgUserId));
|
|
248
|
-
}
|
|
249
|
-
async refuse(
|
|
250
|
-
const space = await this.requireVisibleSpaceIn(
|
|
251
|
-
this.assertHolder(actor);
|
|
252
|
-
await this.
|
|
253
|
-
return this.deleteMember(orgId, actor, spaceId, actor.id, "invited", () => ownRowMissing("invited"));
|
|
254
|
-
}
|
|
255
|
-
async leave(
|
|
256
|
-
const space = await this.requireVisibleSpaceIn(
|
|
257
|
-
this.assertHolder(actor);
|
|
258
|
-
await this.
|
|
259
|
-
return this.deleteMember(orgId, actor, spaceId, actor.id, "active", () => ownRowMissing("active"));
|
|
260
|
-
}
|
|
261
|
-
async remove(
|
|
262
|
-
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);
|
|
263
403
|
this.assertHasMembers(space.accessRule);
|
|
264
|
-
await this.assertCanManage(orgId, actor, space.communityId);
|
|
265
404
|
const { updated, previousStatus } = await this.uow.run(async ({ entitlements, spaces, outbox }) => {
|
|
266
405
|
const locked = await this.lockSpace(spaces, orgId, spaceId);
|
|
267
406
|
this.assertHasMembers(locked.accessRule);
|
|
@@ -282,7 +421,7 @@ var EntitlementsServiceImpl = class {
|
|
|
282
421
|
orgId,
|
|
283
422
|
data: next,
|
|
284
423
|
metadata: {
|
|
285
|
-
|
|
424
|
+
actorId: actor.id,
|
|
286
425
|
previousStatus: row.status
|
|
287
426
|
}
|
|
288
427
|
})]);
|
|
@@ -300,12 +439,15 @@ var EntitlementsServiceImpl = class {
|
|
|
300
439
|
});
|
|
301
440
|
return updated;
|
|
302
441
|
}
|
|
303
|
-
async listSpaceMembers(
|
|
304
|
-
const
|
|
305
|
-
|
|
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);
|
|
306
447
|
return this.repo.listSpaceMembers(orgId, spaceId, {
|
|
307
448
|
...query,
|
|
308
|
-
|
|
449
|
+
excluding,
|
|
450
|
+
...actor.permissions.has("communities:moderate", space.communityId) ? {} : { statuses: ["active"] }
|
|
309
451
|
});
|
|
310
452
|
}
|
|
311
453
|
async expireDueGrants(now) {
|
|
@@ -392,8 +534,34 @@ var EntitlementsServiceImpl = class {
|
|
|
392
534
|
events: appended
|
|
393
535
|
});
|
|
394
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
|
+
}
|
|
395
563
|
async requireSpaceIn(orgId, communityId, spaceId) {
|
|
396
|
-
const space = await this.content.getSpace(
|
|
564
|
+
const space = await this.content.getSpace(spaceId, this.system(orgId));
|
|
397
565
|
if (!space || space.communityId !== communityId) {
|
|
398
566
|
this.logger.warn("space members rejected — space not found", {
|
|
399
567
|
orgId,
|
|
@@ -404,11 +572,13 @@ var EntitlementsServiceImpl = class {
|
|
|
404
572
|
}
|
|
405
573
|
return space;
|
|
406
574
|
}
|
|
407
|
-
async requireVisibleSpaceIn(
|
|
575
|
+
async requireVisibleSpaceIn(actor, communityId, spaceId) {
|
|
576
|
+
const orgId = actor.orgId;
|
|
408
577
|
const space = await this.requireSpaceIn(orgId, communityId, spaceId);
|
|
409
|
-
if (seesSpace(
|
|
578
|
+
if (seesSpace(false, space.accessRule, void 0)) return space;
|
|
579
|
+
if (await this.opensEverySpace(actor, communityId)) return space;
|
|
410
580
|
const row = await this.repo.findSpaceMember(orgId, spaceId, actor.id);
|
|
411
|
-
if (!seesSpace(
|
|
581
|
+
if (!seesSpace(false, space.accessRule, row?.status)) throw new NotFoundError("Space", spaceId);
|
|
412
582
|
return space;
|
|
413
583
|
}
|
|
414
584
|
assertHasMembers(accessRule) {
|
|
@@ -418,21 +588,39 @@ var EntitlementsServiceImpl = class {
|
|
|
418
588
|
this.assertHasMembers(accessRule);
|
|
419
589
|
if (accessRule !== expected) throw new ConflictError(accessRule === "by_invite" ? "This space is by invitation" : "This space is by request");
|
|
420
590
|
}
|
|
421
|
-
assertHolder(actor) {
|
|
422
|
-
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
|
+
}
|
|
423
600
|
}
|
|
424
|
-
async
|
|
425
|
-
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");
|
|
426
603
|
}
|
|
427
|
-
async assertHolds(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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);
|
|
431
615
|
}
|
|
432
|
-
async assertCanManage(
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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");
|
|
436
624
|
}
|
|
437
625
|
async accessOf(orgId, orgUserId, communityId, spaceId) {
|
|
438
626
|
const [facts] = await this.repo.accessAmong(orgId, communityId, spaceId, [orgUserId]);
|
|
@@ -450,17 +638,17 @@ var EntitlementsServiceImpl = class {
|
|
|
450
638
|
async insertMember(entitlements, outbox, orgId, actor, input, room) {
|
|
451
639
|
const existing = await entitlements.lockSpaceMember(orgId, input.spaceId, input.orgUserId);
|
|
452
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");
|
|
453
|
-
if (room) await this.assertRoom(entitlements, orgId, room);
|
|
641
|
+
if (room) await this.assertRoom(entitlements, orgId, room.space, room.excluding);
|
|
454
642
|
if (existing) await entitlements.deleteSpaceMember(orgId, existing.id);
|
|
455
643
|
const created = await entitlements.insertSpaceMember(orgId, input);
|
|
456
644
|
await outbox.append([entitlementEvents.spaceMemberCreated.make({
|
|
457
645
|
orgId,
|
|
458
646
|
data: created,
|
|
459
|
-
metadata: {
|
|
647
|
+
metadata: { actorId: actor.id }
|
|
460
648
|
})]);
|
|
461
649
|
return created;
|
|
462
650
|
}
|
|
463
|
-
async activate(orgId, actor, spaceId, orgUserId, rule, from, missing) {
|
|
651
|
+
async activate(orgId, actor, spaceId, orgUserId, rule, from, excluding, missing) {
|
|
464
652
|
const updated = await this.uow.run(async ({ entitlements, spaces, outbox }) => {
|
|
465
653
|
const locked = await this.lockSpace(spaces, orgId, spaceId);
|
|
466
654
|
this.assertRule(locked.accessRule, rule);
|
|
@@ -475,14 +663,14 @@ var EntitlementsServiceImpl = class {
|
|
|
475
663
|
});
|
|
476
664
|
throw missing();
|
|
477
665
|
}
|
|
478
|
-
await this.assertRoom(entitlements, orgId, locked);
|
|
666
|
+
await this.assertRoom(entitlements, orgId, locked, excluding);
|
|
479
667
|
const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, "active");
|
|
480
668
|
if (!next) throw missing();
|
|
481
669
|
await outbox.append([entitlementEvents.spaceMemberUpdated.make({
|
|
482
670
|
orgId,
|
|
483
671
|
data: next,
|
|
484
672
|
metadata: {
|
|
485
|
-
|
|
673
|
+
actorId: actor.id,
|
|
486
674
|
previousStatus: from
|
|
487
675
|
}
|
|
488
676
|
})]);
|
|
@@ -514,7 +702,7 @@ var EntitlementsServiceImpl = class {
|
|
|
514
702
|
orgId,
|
|
515
703
|
data: current,
|
|
516
704
|
metadata: {
|
|
517
|
-
|
|
705
|
+
actorId: actor.id,
|
|
518
706
|
previousStatus: from
|
|
519
707
|
}
|
|
520
708
|
})]);
|
|
@@ -534,17 +722,36 @@ var EntitlementsServiceImpl = class {
|
|
|
534
722
|
if (!locked) throw new NotFoundError("Space", spaceId);
|
|
535
723
|
return locked;
|
|
536
724
|
}
|
|
537
|
-
async assertRoom(entitlements, orgId, space) {
|
|
725
|
+
async assertRoom(entitlements, orgId, space, excluding) {
|
|
538
726
|
if (space.capacity === null) return;
|
|
539
|
-
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");
|
|
540
728
|
}
|
|
541
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
|
+
}
|
|
542
740
|
function alreadyPlaced(actor, orgUserId) {
|
|
543
741
|
return new ConflictError(orgUserId === actor.id ? "You already have a place in this space" : "That person already has a place in this space");
|
|
544
742
|
}
|
|
545
743
|
function ownRowMissing(status) {
|
|
546
744
|
return new ConflictError(status === "invited" ? "You do not have an invitation to this space" : "You are not a member of this space");
|
|
547
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
|
+
}
|
|
548
755
|
//#endregion
|
|
549
756
|
export { EntitlementsServiceImpl };
|
|
550
757
|
|