@headless-lms/core 0.2.0 → 0.3.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 +39 -21
- package/dist/assets/events.js +1 -1
- package/dist/assets/events.js.map +1 -1
- package/dist/assets/file-urls.d.ts +20 -0
- package/dist/assets/file-urls.js +25 -0
- package/dist/assets/file-urls.js.map +1 -0
- package/dist/assets/index.d.ts +5 -4
- package/dist/assets/index.js +3 -2
- package/dist/assets/model.d.ts +2 -2
- package/dist/assets/ports.d.ts +45 -15
- package/dist/assets/service.d.ts +39 -7
- package/dist/assets/service.js +291 -38
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.d.ts +13 -24
- package/dist/automations/actions.js +168 -43
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.d.ts +2 -1
- package/dist/automations/catalog.js +229 -13
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.d.ts +14 -0
- package/dist/automations/community-derivations.js +439 -0
- package/dist/automations/community-derivations.js.map +1 -0
- package/dist/automations/community-mail.d.ts +112 -0
- package/dist/automations/community-mail.js +90 -0
- package/dist/automations/community-mail.js.map +1 -0
- package/dist/automations/community-notifications.d.ts +16 -0
- package/dist/automations/community-notifications.js +84 -0
- package/dist/automations/community-notifications.js.map +1 -0
- package/dist/automations/events.d.ts +28 -0
- package/dist/automations/index.d.ts +4 -2
- package/dist/automations/index.js +3 -2
- package/dist/automations/model.d.ts +10 -3
- package/dist/automations/model.js +15 -2
- package/dist/automations/model.js.map +1 -1
- package/dist/automations/ports.d.ts +5 -1
- package/dist/automations/schedule.d.ts +6 -0
- package/dist/automations/schedule.js +41 -0
- package/dist/automations/schedule.js.map +1 -0
- package/dist/automations/seeds.d.ts +17 -0
- package/dist/automations/seeds.js +235 -0
- package/dist/automations/seeds.js.map +1 -0
- package/dist/automations/service.d.ts +22 -3
- package/dist/automations/service.js +306 -43
- package/dist/automations/service.js.map +1 -1
- package/dist/content/events.d.ts +631 -19
- package/dist/content/events.js +44 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +4 -4
- package/dist/content/model.d.ts +2 -2
- package/dist/content/ports.d.ts +71 -18
- package/dist/content/service.d.ts +57 -18
- package/dist/content/service.js +502 -23
- package/dist/content/service.js.map +1 -1
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +10 -0
- package/dist/discussion/authors.js +23 -0
- package/dist/discussion/authors.js.map +1 -0
- package/dist/discussion/chats.d.ts +104 -0
- package/dist/discussion/chats.js +798 -0
- package/dist/discussion/chats.js.map +1 -0
- package/dist/discussion/events.d.ts +619 -56
- package/dist/discussion/events.js +106 -3
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +7 -3
- package/dist/discussion/index.js +4 -1
- package/dist/discussion/ports.d.ts +288 -27
- package/dist/discussion/ports.js +6 -1
- package/dist/discussion/ports.js.map +1 -0
- package/dist/discussion/posts.d.ts +103 -0
- package/dist/discussion/posts.js +723 -0
- package/dist/discussion/posts.js.map +1 -0
- package/dist/discussion/service.d.ts +32 -10
- package/dist/discussion/service.js +293 -78
- package/dist/discussion/service.js.map +1 -1
- package/dist/entitlements/events.d.ts +178 -11
- package/dist/entitlements/events.js +28 -1
- package/dist/entitlements/events.js.map +1 -1
- package/dist/entitlements/index.d.ts +5 -4
- package/dist/entitlements/index.js +2 -1
- package/dist/entitlements/model.d.ts +3 -2
- package/dist/entitlements/ports.d.ts +124 -11
- package/dist/entitlements/rules.d.ts +12 -0
- package/dist/entitlements/rules.js +17 -0
- package/dist/entitlements/rules.js.map +1 -0
- package/dist/entitlements/service.d.ts +54 -3
- package/dist/entitlements/service.js +472 -2
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/access.d.ts +6 -0
- package/dist/identity/access.js +7 -0
- package/dist/identity/access.js.map +1 -0
- package/dist/identity/index.d.ts +6 -4
- package/dist/identity/index.js +3 -1
- package/dist/identity/model.d.ts +2 -1
- package/dist/identity/notifications.d.ts +27 -0
- package/dist/identity/notifications.js +101 -0
- package/dist/identity/notifications.js.map +1 -0
- package/dist/identity/ports.d.ts +73 -3
- package/dist/identity/service.d.ts +14 -3
- package/dist/identity/service.js +62 -5
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/model.js +1 -1
- package/dist/integrations/model.js.map +1 -1
- package/dist/integrations/service.js +2 -2
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/access-scopes.d.ts +19 -0
- package/dist/organizations/access-scopes.js +89 -0
- package/dist/organizations/access-scopes.js.map +1 -0
- package/dist/organizations/events.d.ts +74 -32
- package/dist/organizations/events.js +6 -0
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +7 -6
- package/dist/organizations/index.js +4 -3
- package/dist/organizations/members.d.ts +8 -5
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/ports.d.ts +14 -11
- package/dist/organizations/roles.d.ts +6 -10
- package/dist/organizations/roles.js +19 -7
- package/dist/organizations/roles.js.map +1 -1
- package/dist/organizations/service.d.ts +20 -11
- package/dist/organizations/service.js +157 -47
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/types.d.ts +1 -10
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +6 -1
- package/dist/progress/service.d.ts +3 -1
- package/dist/progress/service.js +6 -2
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +4 -0
- package/dist/reporting/admin/index.js +2 -0
- package/dist/reporting/admin/model.d.ts +108 -0
- package/dist/reporting/admin/ports.d.ts +27 -0
- package/dist/reporting/admin/service.d.ts +22 -0
- package/dist/reporting/admin/service.js +38 -0
- package/dist/reporting/admin/service.js.map +1 -0
- package/dist/reporting/member/index.d.ts +5 -0
- package/dist/reporting/member/index.js +2 -0
- package/dist/reporting/member/model.d.ts +124 -0
- package/dist/reporting/member/ports.d.ts +45 -0
- package/dist/reporting/member/service.d.ts +73 -0
- package/dist/reporting/member/service.js +286 -0
- package/dist/reporting/member/service.js.map +1 -0
- package/dist/shared/actor.d.ts +9 -0
- package/dist/shared/errors.d.ts +7 -1
- package/dist/shared/errors.js +10 -1
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +11 -0
- package/dist/shared/id.js +12 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/mailer.d.ts +4 -1
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/network.d.ts +10 -0
- package/dist/shared/network.js +211 -0
- package/dist/shared/network.js.map +1 -0
- package/dist/types/assets.d.ts +2 -2
- package/dist/types/automations.d.ts +10 -2
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +11 -3
- package/dist/types/email-templates.d.ts +121 -0
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +48 -2
- package/dist/types/index.d.ts +9 -9
- package/dist/types/ports.d.ts +6 -0
- package/dist/types/schemas/assets.d.ts +65 -37
- package/dist/types/schemas/assets.js +17 -12
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +18 -1
- package/dist/types/schemas/automations.js +15 -6
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +335 -3
- package/dist/types/schemas/content.js +139 -3
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +370 -3
- package/dist/types/schemas/discussion.js +199 -4
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +36 -2
- package/dist/types/schemas/entitlements.js +23 -1
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +111 -1
- package/dist/types/schemas/identity.js +55 -2
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +8 -8
- package/dist/types/schemas/index.js +8 -8
- package/dist/types/schemas/organizations.d.ts +8 -0
- package/dist/types/schemas/organizations.js +2 -0
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/shared.d.ts +3 -1
- package/dist/types/schemas/shared.js +5 -1
- package/dist/types/schemas/shared.js.map +1 -1
- package/package.json +2 -2
- package/dist/reporting/courses/index.d.ts +0 -4
- package/dist/reporting/courses/index.js +0 -2
- package/dist/reporting/courses/model.d.ts +0 -24
- package/dist/reporting/courses/ports.d.ts +0 -13
- package/dist/reporting/courses/service.d.ts +0 -19
- package/dist/reporting/courses/service.js +0 -20
- package/dist/reporting/courses/service.js.map +0 -1
- package/dist/reporting/dashboard/index.d.ts +0 -4
- package/dist/reporting/dashboard/index.js +0 -2
- package/dist/reporting/dashboard/model.d.ts +0 -14
- package/dist/reporting/dashboard/ports.d.ts +0 -13
- package/dist/reporting/dashboard/service.d.ts +0 -19
- package/dist/reporting/dashboard/service.js +0 -20
- package/dist/reporting/dashboard/service.js.map +0 -1
- package/dist/reporting/learn/index.d.ts +0 -5
- package/dist/reporting/learn/index.js +0 -2
- package/dist/reporting/learn/model.d.ts +0 -23
- package/dist/reporting/learn/ports.d.ts +0 -43
- package/dist/reporting/learn/ports.js +0 -1
- package/dist/reporting/learn/service.d.ts +0 -49
- package/dist/reporting/learn/service.js +0 -116
- package/dist/reporting/learn/service.js.map +0 -1
- package/dist/reporting/students/index.d.ts +0 -4
- package/dist/reporting/students/index.js +0 -2
- package/dist/reporting/students/model.d.ts +0 -51
- package/dist/reporting/students/model.js +0 -1
- package/dist/reporting/students/ports.d.ts +0 -15
- package/dist/reporting/students/ports.js +0 -1
- package/dist/reporting/students/service.d.ts +0 -20
- package/dist/reporting/students/service.js +0 -23
- package/dist/reporting/students/service.js.map +0 -1
- /package/dist/reporting/{courses → admin}/model.js +0 -0
- /package/dist/reporting/{courses → admin}/ports.js +0 -0
- /package/dist/reporting/{dashboard → member}/model.js +0 -0
- /package/dist/reporting/{dashboard → member}/ports.js +0 -0
- /package/dist/{reporting/learn/model.js → shared/actor.js} +0 -0
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import { ConflictError, ForbiddenError, NotFoundError } from "../shared/errors.js";
|
|
1
2
|
import { noopLogger } from "../shared/logger.js";
|
|
3
|
+
import { canModerate, isStaffRole } from "../organizations/roles.js";
|
|
2
4
|
import { entitlementEvents } from "./events.js";
|
|
5
|
+
import { laterExpiry, opensSpace, seesSpace } from "./rules.js";
|
|
3
6
|
//#region src/entitlements/service.ts
|
|
7
|
+
const ENDING_SOON_MS = 10080 * 60 * 1e3;
|
|
8
|
+
const NOTICE_BATCH_SIZE = 100;
|
|
4
9
|
var EntitlementsServiceImpl = class {
|
|
5
10
|
repo;
|
|
11
|
+
content;
|
|
6
12
|
uow;
|
|
7
13
|
logger;
|
|
8
14
|
constructor(params) {
|
|
9
15
|
this.repo = params.repo;
|
|
16
|
+
this.content = params.content;
|
|
10
17
|
this.uow = params.uow;
|
|
11
18
|
this.logger = params.logger ?? noopLogger;
|
|
12
19
|
}
|
|
@@ -31,6 +38,50 @@ var EntitlementsServiceImpl = class {
|
|
|
31
38
|
});
|
|
32
39
|
return entitlement;
|
|
33
40
|
}
|
|
41
|
+
async ensureGrant(orgId, input) {
|
|
42
|
+
const { entitlement, change } = await this.uow.run(async ({ entitlements, outbox }) => {
|
|
43
|
+
const created = await entitlements.insertIfAbsent(orgId, input);
|
|
44
|
+
if (created) {
|
|
45
|
+
await outbox.append([entitlementEvents.entitlementCreated.make({
|
|
46
|
+
orgId,
|
|
47
|
+
data: created
|
|
48
|
+
})]);
|
|
49
|
+
return {
|
|
50
|
+
entitlement: created,
|
|
51
|
+
change: "created"
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const existing = await entitlements.lockGrant(orgId, input);
|
|
55
|
+
if (!existing) throw new ConflictError("The grant changed while it was being given");
|
|
56
|
+
if (existing.status === "revoked") return {
|
|
57
|
+
entitlement: existing,
|
|
58
|
+
change: null
|
|
59
|
+
};
|
|
60
|
+
const expiresAt = laterExpiry(existing.expiresAt, input.expiresAt ?? null);
|
|
61
|
+
if (expiresAt?.getTime() === existing.expiresAt?.getTime()) return {
|
|
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({
|
|
68
|
+
orgId,
|
|
69
|
+
data: updated
|
|
70
|
+
})]);
|
|
71
|
+
return {
|
|
72
|
+
entitlement: updated,
|
|
73
|
+
change: "updated"
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
if (change) this.logger.info(change === "created" ? "entitlement granted" : "entitlement extended", {
|
|
77
|
+
orgId,
|
|
78
|
+
entitlementId: entitlement.id,
|
|
79
|
+
orgUserId: entitlement.orgUserId,
|
|
80
|
+
bundleId: entitlement.bundleId,
|
|
81
|
+
contentId: entitlement.contentId
|
|
82
|
+
});
|
|
83
|
+
return entitlement;
|
|
84
|
+
}
|
|
34
85
|
async setStatus(orgId, id, status) {
|
|
35
86
|
const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {
|
|
36
87
|
const updated = await entitlements.setStatus(orgId, id, status);
|
|
@@ -51,10 +102,429 @@ var EntitlementsServiceImpl = class {
|
|
|
51
102
|
});
|
|
52
103
|
return entitlement;
|
|
53
104
|
}
|
|
54
|
-
|
|
55
|
-
return this.repo.
|
|
105
|
+
hasAccess(orgId, orgUserId, contentId) {
|
|
106
|
+
return this.repo.hasAccess(orgId, orgUserId, contentId);
|
|
107
|
+
}
|
|
108
|
+
listAccessibleContent(orgId, orgUserId, type) {
|
|
109
|
+
return this.repo.listAccessibleContent(orgId, orgUserId, type);
|
|
110
|
+
}
|
|
111
|
+
async isCommunityMember(orgId, actor, communityId) {
|
|
112
|
+
return (await this.accessOf(orgId, actor.id, communityId, null))?.member === true;
|
|
113
|
+
}
|
|
114
|
+
async canOpenSpace(orgId, actor, spaceId) {
|
|
115
|
+
const space = await this.content.getSpace(orgId, spaceId);
|
|
116
|
+
if (!space) return false;
|
|
117
|
+
return this.canOpen(orgId, actor, space);
|
|
118
|
+
}
|
|
119
|
+
async canOpen(orgId, actor, space) {
|
|
120
|
+
const facts = await this.accessOf(orgId, actor.id, space.communityId, space.id);
|
|
121
|
+
if (!facts) return false;
|
|
122
|
+
return opensSpace(facts.role, facts.member, space.accessRule, facts.activeInSpace);
|
|
123
|
+
}
|
|
124
|
+
async spaceStandings(orgId, actor, communityId) {
|
|
125
|
+
const rows = await this.repo.listSpaceMembersFor(orgId, actor.id, communityId);
|
|
126
|
+
return Object.fromEntries(rows.map((row) => [row.spaceId, row.status]));
|
|
127
|
+
}
|
|
128
|
+
async openSpacesFor(orgId, actor, communityId) {
|
|
129
|
+
const facts = await this.accessOf(orgId, actor.id, communityId, null);
|
|
130
|
+
if (!facts?.member) return [];
|
|
131
|
+
const [spaces, activeIds] = await Promise.all([this.content.listSpaces(orgId, communityId), this.repo.activeSpaceIds(orgId, actor.id, communityId)]);
|
|
132
|
+
const active = new Set(activeIds);
|
|
133
|
+
return spaces.filter((space) => opensSpace(facts.role, facts.member, space.accessRule, active.has(space.id)));
|
|
134
|
+
}
|
|
135
|
+
async openSpaceIds(orgId, actor, communityId) {
|
|
136
|
+
return (await this.openSpacesFor(orgId, actor, communityId)).map((space) => space.id);
|
|
137
|
+
}
|
|
138
|
+
async communityMembersAmong(orgId, communityId, orgUserIds) {
|
|
139
|
+
if (orgUserIds.length === 0) return [];
|
|
140
|
+
return (await this.repo.accessAmong(orgId, communityId, null, orgUserIds)).filter((fact) => fact.member).map((fact) => fact.orgUserId);
|
|
141
|
+
}
|
|
142
|
+
async spaceOpenersAmong(orgId, spaceId, orgUserIds) {
|
|
143
|
+
if (orgUserIds.length === 0) return [];
|
|
144
|
+
const space = await this.content.getSpace(orgId, spaceId);
|
|
145
|
+
if (!space) return [];
|
|
146
|
+
return (await this.repo.accessAmong(orgId, space.communityId, space.id, orgUserIds)).filter((fact) => opensSpace(fact.role, fact.member, space.accessRule, fact.activeInSpace)).map((fact) => fact.orgUserId);
|
|
147
|
+
}
|
|
148
|
+
async spaceOpeners(orgId, spaceId) {
|
|
149
|
+
const space = await this.content.getSpace(orgId, spaceId);
|
|
150
|
+
if (!space) return [];
|
|
151
|
+
const members = await this.repo.communityMemberIds(orgId, space.communityId);
|
|
152
|
+
return this.spaceOpenersAmong(orgId, spaceId, members);
|
|
153
|
+
}
|
|
154
|
+
communitiesOpenedBy(orgId, grant) {
|
|
155
|
+
return this.repo.communitiesOpenedBy(orgId, grant);
|
|
156
|
+
}
|
|
157
|
+
communityModeratorIds(orgId, communityId) {
|
|
158
|
+
return this.repo.communityModeratorIds(orgId, communityId);
|
|
159
|
+
}
|
|
160
|
+
communityIdsOpenedFor(orgId, orgUserId) {
|
|
161
|
+
return this.repo.communityIdsOpenedFor(orgId, orgUserId);
|
|
162
|
+
}
|
|
163
|
+
countActiveSpaceMembers(orgId, spaceId) {
|
|
164
|
+
return this.repo.countActiveSpaceMembers(orgId, spaceId);
|
|
165
|
+
}
|
|
166
|
+
async requestJoin(orgId, actor, communityId, spaceId) {
|
|
167
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
168
|
+
this.assertRule(space.accessRule, "by_request");
|
|
169
|
+
if (canModerate(actor.role)) throw new ForbiddenError("Staff do not request a place in a space");
|
|
170
|
+
await this.assertGrant(orgId, actor, actor.id, space.communityId);
|
|
171
|
+
const created = await this.createMember(orgId, actor, space, actor.id, "requested", "request");
|
|
172
|
+
this.logger.info("space member created", {
|
|
173
|
+
orgId,
|
|
174
|
+
spaceId,
|
|
175
|
+
orgUserId: actor.id,
|
|
176
|
+
status: created.status,
|
|
177
|
+
actorId: actor.id
|
|
178
|
+
});
|
|
179
|
+
return created;
|
|
180
|
+
}
|
|
181
|
+
async invite(orgId, actor, communityId, spaceId, orgUserId) {
|
|
182
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
183
|
+
this.assertRule(space.accessRule, "by_invite");
|
|
184
|
+
await this.assertCanManage(orgId, actor, space.communityId);
|
|
185
|
+
await this.assertHolds(orgId, actor, orgUserId, space.communityId);
|
|
186
|
+
const created = await this.createMember(orgId, actor, space, orgUserId, "invited", "invite");
|
|
187
|
+
this.logger.info("space member created", {
|
|
188
|
+
orgId,
|
|
189
|
+
spaceId,
|
|
190
|
+
orgUserId,
|
|
191
|
+
status: created.status,
|
|
192
|
+
actorId: actor.id
|
|
193
|
+
});
|
|
194
|
+
return created;
|
|
195
|
+
}
|
|
196
|
+
async addSpaceMember(orgId, actor, communityId, spaceId, orgUserId) {
|
|
197
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
198
|
+
this.assertHasMembers(space.accessRule);
|
|
199
|
+
await this.assertCanManage(orgId, actor, space.communityId);
|
|
200
|
+
await this.assertHolds(orgId, actor, orgUserId, space.communityId);
|
|
201
|
+
const created = await this.uow.run(async (scope) => {
|
|
202
|
+
const locked = await this.lockSpace(scope.spaces, orgId, spaceId);
|
|
203
|
+
this.assertHasMembers(locked.accessRule);
|
|
204
|
+
return this.insertMember(scope.entitlements, scope.outbox, orgId, actor, {
|
|
205
|
+
communityId: space.communityId,
|
|
206
|
+
spaceId,
|
|
207
|
+
orgUserId,
|
|
208
|
+
status: "active",
|
|
209
|
+
source: "manual"
|
|
210
|
+
}, locked);
|
|
211
|
+
});
|
|
212
|
+
this.logger.info("space member created", {
|
|
213
|
+
orgId,
|
|
214
|
+
spaceId,
|
|
215
|
+
orgUserId,
|
|
216
|
+
status: created.status,
|
|
217
|
+
actorId: actor.id
|
|
218
|
+
});
|
|
219
|
+
return created;
|
|
220
|
+
}
|
|
221
|
+
async accept(orgId, actor, communityId, spaceId) {
|
|
222
|
+
this.assertHolder(actor);
|
|
223
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
224
|
+
this.assertRule(space.accessRule, "by_invite");
|
|
225
|
+
await this.assertGrant(orgId, actor, actor.id, space.communityId);
|
|
226
|
+
return this.activate(orgId, actor, spaceId, actor.id, "by_invite", "invited", () => ownRowMissing("invited"));
|
|
227
|
+
}
|
|
228
|
+
async approve(orgId, actor, communityId, spaceId, orgUserId) {
|
|
229
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
230
|
+
this.assertRule(space.accessRule, "by_request");
|
|
231
|
+
await this.assertCanManage(orgId, actor, space.communityId);
|
|
232
|
+
await this.assertGrant(orgId, actor, orgUserId, space.communityId);
|
|
233
|
+
return this.activate(orgId, actor, spaceId, orgUserId, "by_request", "requested", () => new NotFoundError("Space member", orgUserId));
|
|
234
|
+
}
|
|
235
|
+
async decline(orgId, actor, communityId, spaceId, orgUserId) {
|
|
236
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
237
|
+
await this.assertCanManage(orgId, actor, space.communityId);
|
|
238
|
+
return this.deleteMember(orgId, actor, spaceId, orgUserId, "requested", () => new NotFoundError("Space member", orgUserId));
|
|
239
|
+
}
|
|
240
|
+
async refuse(orgId, actor, communityId, spaceId) {
|
|
241
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
242
|
+
this.assertHolder(actor);
|
|
243
|
+
await this.assertGrant(orgId, actor, actor.id, space.communityId);
|
|
244
|
+
return this.deleteMember(orgId, actor, spaceId, actor.id, "invited", () => ownRowMissing("invited"));
|
|
245
|
+
}
|
|
246
|
+
async leave(orgId, actor, communityId, spaceId) {
|
|
247
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
248
|
+
this.assertHolder(actor);
|
|
249
|
+
await this.assertGrant(orgId, actor, actor.id, space.communityId);
|
|
250
|
+
return this.deleteMember(orgId, actor, spaceId, actor.id, "active", () => ownRowMissing("active"));
|
|
251
|
+
}
|
|
252
|
+
async remove(orgId, actor, communityId, spaceId, orgUserId) {
|
|
253
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
254
|
+
this.assertHasMembers(space.accessRule);
|
|
255
|
+
await this.assertCanManage(orgId, actor, space.communityId);
|
|
256
|
+
const { updated, previousStatus } = await this.uow.run(async ({ entitlements, spaces, outbox }) => {
|
|
257
|
+
const locked = await this.lockSpace(spaces, orgId, spaceId);
|
|
258
|
+
this.assertHasMembers(locked.accessRule);
|
|
259
|
+
const row = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);
|
|
260
|
+
if (!row) {
|
|
261
|
+
this.logger.warn("space member remove rejected — not found", {
|
|
262
|
+
orgId,
|
|
263
|
+
spaceId,
|
|
264
|
+
orgUserId,
|
|
265
|
+
actorId: actor.id
|
|
266
|
+
});
|
|
267
|
+
throw new NotFoundError("Space member", orgUserId);
|
|
268
|
+
}
|
|
269
|
+
if (row.status === "revoked") throw new ConflictError("This person is already removed from the space");
|
|
270
|
+
const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, "revoked");
|
|
271
|
+
if (!next) throw new NotFoundError("Space member", row.id);
|
|
272
|
+
await outbox.append([entitlementEvents.spaceMemberUpdated.make({
|
|
273
|
+
orgId,
|
|
274
|
+
data: next,
|
|
275
|
+
metadata: {
|
|
276
|
+
actorOrgUserId: actor.id,
|
|
277
|
+
previousStatus: row.status
|
|
278
|
+
}
|
|
279
|
+
})]);
|
|
280
|
+
return {
|
|
281
|
+
updated: next,
|
|
282
|
+
previousStatus: row.status
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
this.logger.info("space member removed", {
|
|
286
|
+
orgId,
|
|
287
|
+
spaceId,
|
|
288
|
+
orgUserId,
|
|
289
|
+
previousStatus,
|
|
290
|
+
actorId: actor.id
|
|
291
|
+
});
|
|
292
|
+
return updated;
|
|
293
|
+
}
|
|
294
|
+
async listSpaceMembers(orgId, actor, communityId, spaceId, query) {
|
|
295
|
+
const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);
|
|
296
|
+
if (!await this.canOpen(orgId, actor, space)) throw new NotFoundError("Space", spaceId);
|
|
297
|
+
return this.repo.listSpaceMembers(orgId, spaceId, {
|
|
298
|
+
...query,
|
|
299
|
+
...canModerate(actor.role) ? {} : { statuses: ["active"] }
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
hasDueNotices(orgId, now) {
|
|
303
|
+
return this.repo.hasDueNotices(orgId, now, new Date(now.getTime() + ENDING_SOON_MS));
|
|
304
|
+
}
|
|
305
|
+
async expireDue(orgId, now) {
|
|
306
|
+
const until = new Date(now.getTime() + ENDING_SOON_MS);
|
|
307
|
+
let appended = 0;
|
|
308
|
+
let afterId;
|
|
309
|
+
for (;;) {
|
|
310
|
+
const batch = await this.uow.run(async ({ entitlements, outbox }) => {
|
|
311
|
+
const events = [];
|
|
312
|
+
const grants = await entitlements.listGrantsExpiredBy(orgId, now, NOTICE_BATCH_SIZE, afterId);
|
|
313
|
+
for (const grant of grants) {
|
|
314
|
+
if (!grant.expiresAt) continue;
|
|
315
|
+
if (await entitlements.claimNotice(orgId, {
|
|
316
|
+
kind: "expired",
|
|
317
|
+
entitlementId: grant.id,
|
|
318
|
+
expiresAt: grant.expiresAt
|
|
319
|
+
}, now)) events.push(entitlementEvents.entitlementExpired.make({
|
|
320
|
+
orgId,
|
|
321
|
+
data: grant
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
if (events.length > 0) await outbox.append(events);
|
|
325
|
+
return {
|
|
326
|
+
events: events.length,
|
|
327
|
+
last: grants.at(-1)?.id,
|
|
328
|
+
size: grants.length
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
appended += batch.events;
|
|
332
|
+
if (batch.size < NOTICE_BATCH_SIZE || batch.last === void 0) break;
|
|
333
|
+
afterId = batch.last;
|
|
334
|
+
}
|
|
335
|
+
let afterKey;
|
|
336
|
+
for (;;) {
|
|
337
|
+
const batch = await this.uow.run(async ({ entitlements, outbox }) => {
|
|
338
|
+
const events = [];
|
|
339
|
+
const ending = await entitlements.listGrantsEndingBetween(orgId, now, until, NOTICE_BATCH_SIZE, afterKey);
|
|
340
|
+
for (const { grant, communityId } of ending) {
|
|
341
|
+
if (!grant.expiresAt) continue;
|
|
342
|
+
if (await entitlements.claimNotice(orgId, {
|
|
343
|
+
kind: "ending_soon",
|
|
344
|
+
entitlementId: grant.id,
|
|
345
|
+
communityId,
|
|
346
|
+
expiresAt: grant.expiresAt
|
|
347
|
+
}, now)) events.push(entitlementEvents.entitlementEndingSoon.make({
|
|
348
|
+
orgId,
|
|
349
|
+
data: {
|
|
350
|
+
...grant,
|
|
351
|
+
communityId
|
|
352
|
+
}
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
if (events.length > 0) await outbox.append(events);
|
|
356
|
+
const last = ending.at(-1);
|
|
357
|
+
return {
|
|
358
|
+
events: events.length,
|
|
359
|
+
last: last && {
|
|
360
|
+
entitlementId: last.grant.id,
|
|
361
|
+
communityId: last.communityId
|
|
362
|
+
},
|
|
363
|
+
size: ending.length
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
appended += batch.events;
|
|
367
|
+
if (batch.size < NOTICE_BATCH_SIZE || batch.last === void 0) break;
|
|
368
|
+
afterKey = batch.last;
|
|
369
|
+
}
|
|
370
|
+
if (appended > 0) this.logger.info("due grants expired", {
|
|
371
|
+
orgId,
|
|
372
|
+
events: appended
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async requireSpaceIn(orgId, communityId, spaceId) {
|
|
376
|
+
const space = await this.content.getSpace(orgId, spaceId);
|
|
377
|
+
if (!space || space.communityId !== communityId) {
|
|
378
|
+
this.logger.warn("space members rejected — space not found", {
|
|
379
|
+
orgId,
|
|
380
|
+
communityId,
|
|
381
|
+
spaceId
|
|
382
|
+
});
|
|
383
|
+
throw new NotFoundError("Space", spaceId);
|
|
384
|
+
}
|
|
385
|
+
return space;
|
|
386
|
+
}
|
|
387
|
+
async requireVisibleSpaceIn(orgId, actor, communityId, spaceId) {
|
|
388
|
+
const space = await this.requireSpaceIn(orgId, communityId, spaceId);
|
|
389
|
+
if (seesSpace(actor.role, space.accessRule, void 0)) return space;
|
|
390
|
+
const row = await this.repo.findSpaceMember(orgId, spaceId, actor.id);
|
|
391
|
+
if (!seesSpace(actor.role, space.accessRule, row?.status)) throw new NotFoundError("Space", spaceId);
|
|
392
|
+
return space;
|
|
393
|
+
}
|
|
394
|
+
assertHasMembers(accessRule) {
|
|
395
|
+
if (accessRule === "all_members") throw new ConflictError("An all-members space has no space members");
|
|
396
|
+
}
|
|
397
|
+
assertRule(accessRule, expected) {
|
|
398
|
+
this.assertHasMembers(accessRule);
|
|
399
|
+
if (accessRule !== expected) throw new ConflictError(accessRule === "by_invite" ? "This space is by invitation" : "This space is by request");
|
|
400
|
+
}
|
|
401
|
+
assertHolder(actor) {
|
|
402
|
+
if (isStaffRole(actor.role)) throw new ForbiddenError("Staff do not hold a place in a space");
|
|
403
|
+
}
|
|
404
|
+
async assertGrant(orgId, actor, orgUserId, communityId) {
|
|
405
|
+
if (!await this.repo.hasCommunityAccess(orgId, orgUserId, communityId)) throw new ForbiddenError(orgUserId === actor.id ? "You are not a member of this community" : "That person is not a member of this community");
|
|
406
|
+
}
|
|
407
|
+
async assertHolds(orgId, actor, orgUserId, communityId) {
|
|
408
|
+
const facts = await this.accessOf(orgId, orgUserId, communityId, null);
|
|
409
|
+
if (facts && isStaffRole(facts.role)) throw new ForbiddenError("Staff do not hold a place in a space");
|
|
410
|
+
await this.assertGrant(orgId, actor, orgUserId, communityId);
|
|
411
|
+
}
|
|
412
|
+
async assertCanManage(orgId, actor, communityId) {
|
|
413
|
+
const facts = await this.accessOf(orgId, actor.id, communityId, null);
|
|
414
|
+
if (facts?.member && canModerate(facts.role)) return;
|
|
415
|
+
throw new ForbiddenError("You cannot manage members of this space");
|
|
416
|
+
}
|
|
417
|
+
async accessOf(orgId, orgUserId, communityId, spaceId) {
|
|
418
|
+
const [facts] = await this.repo.accessAmong(orgId, communityId, spaceId, [orgUserId]);
|
|
419
|
+
return facts ?? null;
|
|
420
|
+
}
|
|
421
|
+
async createMember(orgId, actor, space, orgUserId, status, source) {
|
|
422
|
+
return this.uow.run(async ({ entitlements, outbox }) => this.insertMember(entitlements, outbox, orgId, actor, {
|
|
423
|
+
communityId: space.communityId,
|
|
424
|
+
spaceId: space.id,
|
|
425
|
+
orgUserId,
|
|
426
|
+
status,
|
|
427
|
+
source
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
async insertMember(entitlements, outbox, orgId, actor, input, room) {
|
|
431
|
+
const existing = await entitlements.lockSpaceMember(orgId, input.spaceId, input.orgUserId);
|
|
432
|
+
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);
|
|
434
|
+
if (existing) await entitlements.deleteSpaceMember(orgId, existing.id);
|
|
435
|
+
const created = await entitlements.insertSpaceMember(orgId, input);
|
|
436
|
+
await outbox.append([entitlementEvents.spaceMemberCreated.make({
|
|
437
|
+
orgId,
|
|
438
|
+
data: created,
|
|
439
|
+
metadata: { actorOrgUserId: actor.id }
|
|
440
|
+
})]);
|
|
441
|
+
return created;
|
|
442
|
+
}
|
|
443
|
+
async activate(orgId, actor, spaceId, orgUserId, rule, from, missing) {
|
|
444
|
+
const updated = await this.uow.run(async ({ entitlements, spaces, outbox }) => {
|
|
445
|
+
const locked = await this.lockSpace(spaces, orgId, spaceId);
|
|
446
|
+
this.assertRule(locked.accessRule, rule);
|
|
447
|
+
const row = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);
|
|
448
|
+
if (row?.status === "active") throw alreadyPlaced(actor, orgUserId);
|
|
449
|
+
if (!row || row.status !== from) {
|
|
450
|
+
this.logger.warn("space member activate rejected — not found", {
|
|
451
|
+
orgId,
|
|
452
|
+
spaceId,
|
|
453
|
+
orgUserId,
|
|
454
|
+
actorId: actor.id
|
|
455
|
+
});
|
|
456
|
+
throw missing();
|
|
457
|
+
}
|
|
458
|
+
await this.assertRoom(entitlements, orgId, locked);
|
|
459
|
+
const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, "active");
|
|
460
|
+
if (!next) throw missing();
|
|
461
|
+
await outbox.append([entitlementEvents.spaceMemberUpdated.make({
|
|
462
|
+
orgId,
|
|
463
|
+
data: next,
|
|
464
|
+
metadata: {
|
|
465
|
+
actorOrgUserId: actor.id,
|
|
466
|
+
previousStatus: from
|
|
467
|
+
}
|
|
468
|
+
})]);
|
|
469
|
+
return next;
|
|
470
|
+
});
|
|
471
|
+
this.logger.info("space member activated", {
|
|
472
|
+
orgId,
|
|
473
|
+
spaceId,
|
|
474
|
+
orgUserId,
|
|
475
|
+
actorId: actor.id
|
|
476
|
+
});
|
|
477
|
+
return updated;
|
|
478
|
+
}
|
|
479
|
+
async deleteMember(orgId, actor, spaceId, orgUserId, from, missing) {
|
|
480
|
+
const row = await this.uow.run(async ({ entitlements, outbox }) => {
|
|
481
|
+
const current = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);
|
|
482
|
+
if (from !== "active" && current?.status === "active") throw alreadyPlaced(actor, orgUserId);
|
|
483
|
+
if (!current || current.status !== from) {
|
|
484
|
+
this.logger.warn("space member delete rejected — not found", {
|
|
485
|
+
orgId,
|
|
486
|
+
spaceId,
|
|
487
|
+
orgUserId,
|
|
488
|
+
actorId: actor.id
|
|
489
|
+
});
|
|
490
|
+
throw missing();
|
|
491
|
+
}
|
|
492
|
+
await entitlements.deleteSpaceMember(orgId, current.id);
|
|
493
|
+
await outbox.append([entitlementEvents.spaceMemberDeleted.make({
|
|
494
|
+
orgId,
|
|
495
|
+
data: current,
|
|
496
|
+
metadata: {
|
|
497
|
+
actorOrgUserId: actor.id,
|
|
498
|
+
previousStatus: from
|
|
499
|
+
}
|
|
500
|
+
})]);
|
|
501
|
+
return current;
|
|
502
|
+
});
|
|
503
|
+
this.logger.info("space member deleted", {
|
|
504
|
+
orgId,
|
|
505
|
+
spaceId,
|
|
506
|
+
orgUserId,
|
|
507
|
+
previousStatus: from,
|
|
508
|
+
actorId: actor.id
|
|
509
|
+
});
|
|
510
|
+
return row;
|
|
511
|
+
}
|
|
512
|
+
async lockSpace(spaces, orgId, spaceId) {
|
|
513
|
+
const locked = await spaces.lockSpace(orgId, spaceId);
|
|
514
|
+
if (!locked) throw new NotFoundError("Space", spaceId);
|
|
515
|
+
return locked;
|
|
516
|
+
}
|
|
517
|
+
async assertRoom(entitlements, orgId, space) {
|
|
518
|
+
if (space.capacity === null) return;
|
|
519
|
+
if (await entitlements.countActiveSpaceMembers(orgId, space.id) >= space.capacity) throw new ConflictError("This space is full");
|
|
56
520
|
}
|
|
57
521
|
};
|
|
522
|
+
function alreadyPlaced(actor, orgUserId) {
|
|
523
|
+
return new ConflictError(orgUserId === actor.id ? "You already have a place in this space" : "That person already has a place in this space");
|
|
524
|
+
}
|
|
525
|
+
function ownRowMissing(status) {
|
|
526
|
+
return new ConflictError(status === "invited" ? "You do not have an invitation to this space" : "You are not a member of this space");
|
|
527
|
+
}
|
|
58
528
|
//#endregion
|
|
59
529
|
export { EntitlementsServiceImpl };
|
|
60
530
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../src/entitlements/service.ts"],"sourcesContent":["// entitlements context — service implementation (inbound port).\n//\n// Mutations run inside the context's UnitOfWork: the domain write and the\n// outbox append commit in ONE transaction (transactional outbox). This\n// service never publishes — the outbox relay republishes committed events to\n// the EventBus at-least-once.\nimport type { Entitlement, EntitlementsQuery, GrantEntitlementInput, Page } from './model.js';\nimport type {\n EntitlementsRepository,\n EntitlementsService,\n EntitlementsUnitOfWork,\n} from './ports.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { entitlementEvents } from './events.js';\n\nexport type EntitlementsServiceParams = {\n /** Read-only access (list) — runs outside any transaction. */\n repo: EntitlementsRepository;\n /** Atomic write scope: tx-bound repo + outbox appender. */\n uow: EntitlementsUnitOfWork;\n logger?: Logger;\n};\n\nexport class EntitlementsServiceImpl implements EntitlementsService {\n private readonly repo: EntitlementsRepository;\n private readonly uow: EntitlementsUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: EntitlementsServiceParams) {\n this.repo = params.repo;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n list(orgId: string, query: EntitlementsQuery): Promise<Page<Entitlement>> {\n return this.repo.list(orgId, query);\n }\n\n async grant(orgId: string, input: GrantEntitlementInput): Promise<Entitlement> {\n const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {\n const created = await entitlements.insert(orgId, input);\n await outbox.append([\n entitlementEvents.entitlementCreated.make({\n orgId,\n data: created,\n }),\n ]);\n return created;\n });\n this.logger.info('entitlement granted', {\n orgId,\n entitlementId: entitlement.id,\n orgUserId: entitlement.orgUserId,\n bundleId: entitlement.bundleId,\n contentId: entitlement.contentId,\n });\n return entitlement;\n }\n\n async setStatus(\n orgId: string,\n id: string,\n status: 'active' | 'revoked',\n ): Promise<Entitlement | null> {\n const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {\n const updated = await entitlements.setStatus(orgId, id, status);\n if (!updated) {\n return null;\n }\n await outbox.append([\n status === 'revoked'\n ? entitlementEvents.entitlementDeleted.make({\n orgId,\n data: updated,\n })\n : entitlementEvents.entitlementUpdated.make({\n orgId,\n data: updated,\n }),\n ]);\n return updated;\n });\n if (entitlement) {\n this.logger.info('entitlement status changed', { orgId, entitlementId: id, status });\n }\n return entitlement;\n }\n\n hasCourseAccess(orgId: string, orgUserId: string, courseId: string): Promise<boolean> {\n return this.repo.hasCourseAccess(orgId, orgUserId, courseId);\n }\n}\n"],"mappings":";;;AAwBA,IAAa,0BAAb,MAAoE;CAClE;CACA;CACA;CAEA,YAAY,QAAmC;EAC7C,KAAK,OAAO,OAAO;EACnB,KAAK,MAAM,OAAO;EAClB,KAAK,SAAS,OAAO,UAAU;CACjC;CAEA,KAAK,OAAe,OAAsD;EACxE,OAAO,KAAK,KAAK,KAAK,OAAO,KAAK;CACpC;CAEA,MAAM,MAAM,OAAe,OAAoD;EAC7E,MAAM,cAAc,MAAM,KAAK,IAAI,IAAI,OAAO,EAAE,cAAc,aAAa;GACzE,MAAM,UAAU,MAAM,aAAa,OAAO,OAAO,KAAK;GACtD,MAAM,OAAO,OAAO,CAClB,kBAAkB,mBAAmB,KAAK;IACxC;IACA,MAAM;GACR,CAAC,CACH,CAAC;GACD,OAAO;EACT,CAAC;EACD,KAAK,OAAO,KAAK,uBAAuB;GACtC;GACA,eAAe,YAAY;GAC3B,WAAW,YAAY;GACvB,UAAU,YAAY;GACtB,WAAW,YAAY;EACzB,CAAC;EACD,OAAO;CACT;CAEA,MAAM,UACJ,OACA,IACA,QAC6B;EAC7B,MAAM,cAAc,MAAM,KAAK,IAAI,IAAI,OAAO,EAAE,cAAc,aAAa;GACzE,MAAM,UAAU,MAAM,aAAa,UAAU,OAAO,IAAI,MAAM;GAC9D,IAAI,CAAC,SACH,OAAO;GAET,MAAM,OAAO,OAAO,CAClB,WAAW,YACP,kBAAkB,mBAAmB,KAAK;IACxC;IACA,MAAM;GACR,CAAC,IACD,kBAAkB,mBAAmB,KAAK;IACxC;IACA,MAAM;GACR,CAAC,CACP,CAAC;GACD,OAAO;EACT,CAAC;EACD,IAAI,aACF,KAAK,OAAO,KAAK,8BAA8B;GAAE;GAAO,eAAe;GAAI;EAAO,CAAC;EAErF,OAAO;CACT;CAEA,gBAAgB,OAAe,WAAmB,UAAoC;EACpF,OAAO,KAAK,KAAK,gBAAgB,OAAO,WAAW,QAAQ;CAC7D;AACF"}
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../src/entitlements/service.ts"],"sourcesContent":["// entitlements context — service implementation (inbound port).\n//\n// Mutations run inside the context's UnitOfWork: the domain write and the\n// outbox append commit in ONE transaction (transactional outbox). This\n// service never publishes — the outbox relay republishes committed events to\n// the EventBus at-least-once.\nimport type { CommunityReader, Space, SpaceRowLock } from '../content/index.js';\nimport type {\n ContentType,\n Entitlement,\n EntitlementsQuery,\n GrantEntitlementInput,\n Page,\n SpaceMember,\n} from './model.js';\nimport type {\n AccessFacts,\n EntitlementsRepository,\n EntitlementsService,\n EntitlementsUnitOfWork,\n EndingGrantKey,\n GrantTarget,\n InsertSpaceMemberInput,\n OpenableSpace,\n SpaceAccessRule,\n} from './ports.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { Logger, NewDomainEvent, OutboxAppender } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { ConflictError, ForbiddenError, NotFoundError } from '../shared/errors.js';\nimport { canModerate, isStaffRole } from '../organizations/index.js';\nimport { entitlementEvents } from './events.js';\nimport { laterExpiry, opensSpace, seesSpace } from './rules.js';\n\nconst ENDING_SOON_MS = 7 * 24 * 60 * 60 * 1000;\nconst NOTICE_BATCH_SIZE = 100;\n\nexport type EntitlementsServiceParams = {\n /** Read-only access (list) — runs outside any transaction. */\n repo: EntitlementsRepository;\n content: CommunityReader;\n /** Atomic write scope: tx-bound repo + outbox appender. */\n uow: EntitlementsUnitOfWork;\n logger?: Logger;\n};\n\nexport class EntitlementsServiceImpl implements EntitlementsService {\n private readonly repo: EntitlementsRepository;\n private readonly content: CommunityReader;\n private readonly uow: EntitlementsUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: EntitlementsServiceParams) {\n this.repo = params.repo;\n this.content = params.content;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n list(orgId: string, query: EntitlementsQuery): Promise<Page<Entitlement>> {\n return this.repo.list(orgId, query);\n }\n\n async grant(orgId: string, input: GrantEntitlementInput): Promise<Entitlement> {\n const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {\n const created = await entitlements.insert(orgId, input);\n await outbox.append([\n entitlementEvents.entitlementCreated.make({\n orgId,\n data: created,\n }),\n ]);\n return created;\n });\n this.logger.info('entitlement granted', {\n orgId,\n entitlementId: entitlement.id,\n orgUserId: entitlement.orgUserId,\n bundleId: entitlement.bundleId,\n contentId: entitlement.contentId,\n });\n return entitlement;\n }\n\n async ensureGrant(orgId: string, input: GrantEntitlementInput): Promise<Entitlement> {\n const { entitlement, change } = await this.uow.run(async ({ entitlements, outbox }) => {\n const created = await entitlements.insertIfAbsent(orgId, input);\n if (created) {\n await outbox.append([entitlementEvents.entitlementCreated.make({ orgId, data: created })]);\n return { entitlement: created, change: 'created' as const };\n }\n const existing = await entitlements.lockGrant(orgId, input);\n if (!existing) {\n throw new ConflictError('The grant changed while it was being given');\n }\n if (existing.status === 'revoked') {\n return { entitlement: existing, change: null };\n }\n const expiresAt = laterExpiry(existing.expiresAt, input.expiresAt ?? null);\n if (expiresAt?.getTime() === existing.expiresAt?.getTime()) {\n return { entitlement: existing, change: null };\n }\n const updated = await entitlements.setExpiresAt(orgId, existing.id, expiresAt);\n if (!updated) {\n throw new NotFoundError('Entitlement', existing.id);\n }\n await outbox.append([entitlementEvents.entitlementUpdated.make({ orgId, data: updated })]);\n return { entitlement: updated, change: 'updated' as const };\n });\n if (change) {\n this.logger.info(change === 'created' ? 'entitlement granted' : 'entitlement extended', {\n orgId,\n entitlementId: entitlement.id,\n orgUserId: entitlement.orgUserId,\n bundleId: entitlement.bundleId,\n contentId: entitlement.contentId,\n });\n }\n return entitlement;\n }\n\n async setStatus(\n orgId: string,\n id: string,\n status: 'active' | 'revoked',\n ): Promise<Entitlement | null> {\n const entitlement = await this.uow.run(async ({ entitlements, outbox }) => {\n const updated = await entitlements.setStatus(orgId, id, status);\n if (!updated) {\n return null;\n }\n await outbox.append([\n status === 'revoked'\n ? entitlementEvents.entitlementDeleted.make({\n orgId,\n data: updated,\n })\n : entitlementEvents.entitlementUpdated.make({\n orgId,\n data: updated,\n }),\n ]);\n return updated;\n });\n if (entitlement) {\n this.logger.info('entitlement status changed', { orgId, entitlementId: id, status });\n }\n return entitlement;\n }\n\n hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean> {\n return this.repo.hasAccess(orgId, orgUserId, contentId);\n }\n\n listAccessibleContent(orgId: string, orgUserId: string, type?: ContentType): Promise<string[]> {\n return this.repo.listAccessibleContent(orgId, orgUserId, type);\n }\n\n async isCommunityMember(orgId: string, actor: Actor, communityId: string): Promise<boolean> {\n const facts = await this.accessOf(orgId, actor.id, communityId, null);\n return facts?.member === true;\n }\n\n async canOpenSpace(orgId: string, actor: Actor, spaceId: string): Promise<boolean> {\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space) {\n return false;\n }\n return this.canOpen(orgId, actor, space);\n }\n\n async canOpen(orgId: string, actor: Actor, space: OpenableSpace): Promise<boolean> {\n const facts = await this.accessOf(orgId, actor.id, space.communityId, space.id);\n if (!facts) {\n return false;\n }\n return opensSpace(facts.role, facts.member, space.accessRule, facts.activeInSpace);\n }\n\n async spaceStandings(\n orgId: string,\n actor: Actor,\n communityId: string,\n ): Promise<Record<string, SpaceMember['status']>> {\n const rows = await this.repo.listSpaceMembersFor(orgId, actor.id, communityId);\n return Object.fromEntries(rows.map((row) => [row.spaceId, row.status]));\n }\n\n async openSpacesFor(orgId: string, actor: Actor, communityId: string): Promise<Space[]> {\n const facts = await this.accessOf(orgId, actor.id, communityId, null);\n if (!facts?.member) {\n return [];\n }\n const [spaces, activeIds] = await Promise.all([\n this.content.listSpaces(orgId, communityId),\n this.repo.activeSpaceIds(orgId, actor.id, communityId),\n ]);\n const active = new Set(activeIds);\n return spaces.filter((space) =>\n opensSpace(facts.role, facts.member, space.accessRule, active.has(space.id)),\n );\n }\n\n async openSpaceIds(orgId: string, actor: Actor, communityId: string): Promise<string[]> {\n const spaces = await this.openSpacesFor(orgId, actor, communityId);\n return spaces.map((space) => space.id);\n }\n\n async communityMembersAmong(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<string[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n const facts = await this.repo.accessAmong(orgId, communityId, null, orgUserIds);\n return facts.filter((fact) => fact.member).map((fact) => fact.orgUserId);\n }\n\n async spaceOpenersAmong(orgId: string, spaceId: string, orgUserIds: string[]): Promise<string[]> {\n if (orgUserIds.length === 0) {\n return [];\n }\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space) {\n return [];\n }\n const facts = await this.repo.accessAmong(orgId, space.communityId, space.id, orgUserIds);\n return facts\n .filter((fact) => opensSpace(fact.role, fact.member, space.accessRule, fact.activeInSpace))\n .map((fact) => fact.orgUserId);\n }\n\n async spaceOpeners(orgId: string, spaceId: string): Promise<string[]> {\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space) {\n return [];\n }\n const members = await this.repo.communityMemberIds(orgId, space.communityId);\n return this.spaceOpenersAmong(orgId, spaceId, members);\n }\n\n communitiesOpenedBy(orgId: string, grant: GrantTarget): Promise<string[]> {\n return this.repo.communitiesOpenedBy(orgId, grant);\n }\n\n communityModeratorIds(orgId: string, communityId: string): Promise<string[]> {\n return this.repo.communityModeratorIds(orgId, communityId);\n }\n\n communityIdsOpenedFor(orgId: string, orgUserId: string): Promise<string[]> {\n return this.repo.communityIdsOpenedFor(orgId, orgUserId);\n }\n\n countActiveSpaceMembers(orgId: string, spaceId: string): Promise<number> {\n return this.repo.countActiveSpaceMembers(orgId, spaceId);\n }\n\n async requestJoin(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_request');\n if (canModerate(actor.role)) {\n throw new ForbiddenError('Staff do not request a place in a space');\n }\n await this.assertGrant(orgId, actor, actor.id, space.communityId);\n const created = await this.createMember(orgId, actor, space, actor.id, 'requested', 'request');\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId: actor.id,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async invite(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_invite');\n await this.assertCanManage(orgId, actor, space.communityId);\n await this.assertHolds(orgId, actor, orgUserId, space.communityId);\n const created = await this.createMember(orgId, actor, space, orgUserId, 'invited', 'invite');\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async addSpaceMember(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertHasMembers(space.accessRule);\n await this.assertCanManage(orgId, actor, space.communityId);\n await this.assertHolds(orgId, actor, orgUserId, space.communityId);\n const created = await this.uow.run(async (scope) => {\n const locked = await this.lockSpace(scope.spaces, orgId, spaceId);\n this.assertHasMembers(locked.accessRule);\n return this.insertMember(\n scope.entitlements,\n scope.outbox,\n orgId,\n actor,\n {\n communityId: space.communityId,\n spaceId,\n orgUserId,\n status: 'active',\n source: 'manual',\n },\n locked,\n );\n });\n this.logger.info('space member created', {\n orgId,\n spaceId,\n orgUserId,\n status: created.status,\n actorId: actor.id,\n });\n return created;\n }\n\n async accept(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceMember> {\n this.assertHolder(actor);\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_invite');\n await this.assertGrant(orgId, actor, actor.id, space.communityId);\n return this.activate(orgId, actor, spaceId, actor.id, 'by_invite', 'invited', () =>\n ownRowMissing('invited'),\n );\n }\n\n async approve(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertRule(space.accessRule, 'by_request');\n await this.assertCanManage(orgId, actor, space.communityId);\n await this.assertGrant(orgId, actor, orgUserId, space.communityId);\n return this.activate(\n orgId,\n actor,\n spaceId,\n orgUserId,\n 'by_request',\n 'requested',\n () => new NotFoundError('Space member', orgUserId),\n );\n }\n\n async decline(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n await this.assertCanManage(orgId, actor, space.communityId);\n return this.deleteMember(\n orgId,\n actor,\n spaceId,\n orgUserId,\n 'requested',\n () => new NotFoundError('Space member', orgUserId),\n );\n }\n\n async refuse(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertHolder(actor);\n await this.assertGrant(orgId, actor, actor.id, space.communityId);\n return this.deleteMember(orgId, actor, spaceId, actor.id, 'invited', () =>\n ownRowMissing('invited'),\n );\n }\n\n async leave(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertHolder(actor);\n await this.assertGrant(orgId, actor, actor.id, space.communityId);\n return this.deleteMember(orgId, actor, spaceId, actor.id, 'active', () =>\n ownRowMissing('active'),\n );\n }\n\n async remove(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n orgUserId: string,\n ): Promise<SpaceMember> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n this.assertHasMembers(space.accessRule);\n await this.assertCanManage(orgId, actor, space.communityId);\n const { updated, previousStatus } = await this.uow.run(\n async ({ entitlements, spaces, outbox }) => {\n const locked = await this.lockSpace(spaces, orgId, spaceId);\n this.assertHasMembers(locked.accessRule);\n const row = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (!row) {\n this.logger.warn('space member remove rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw new NotFoundError('Space member', orgUserId);\n }\n if (row.status === 'revoked') {\n throw new ConflictError('This person is already removed from the space');\n }\n const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, 'revoked');\n if (!next) {\n throw new NotFoundError('Space member', row.id);\n }\n await outbox.append([\n entitlementEvents.spaceMemberUpdated.make({\n orgId,\n data: next,\n metadata: { actorOrgUserId: actor.id, previousStatus: row.status },\n }),\n ]);\n return { updated: next, previousStatus: row.status };\n },\n );\n this.logger.info('space member removed', {\n orgId,\n spaceId,\n orgUserId,\n previousStatus,\n actorId: actor.id,\n });\n return updated;\n }\n\n async listSpaceMembers(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n query: { page: number; pageSize: number },\n ): Promise<Page<SpaceMember>> {\n const space = await this.requireVisibleSpaceIn(orgId, actor, communityId, spaceId);\n if (!(await this.canOpen(orgId, actor, space))) {\n throw new NotFoundError('Space', spaceId);\n }\n return this.repo.listSpaceMembers(orgId, spaceId, {\n ...query,\n ...(canModerate(actor.role) ? {} : { statuses: ['active'] }),\n });\n }\n\n hasDueNotices(orgId: string, now: Date): Promise<boolean> {\n return this.repo.hasDueNotices(orgId, now, new Date(now.getTime() + ENDING_SOON_MS));\n }\n\n async expireDue(orgId: string, now: Date): Promise<void> {\n const until = new Date(now.getTime() + ENDING_SOON_MS);\n let appended = 0;\n let afterId: string | undefined;\n for (;;) {\n const batch = await this.uow.run(async ({ entitlements, outbox }) => {\n const events: NewDomainEvent[] = [];\n const grants = await entitlements.listGrantsExpiredBy(\n orgId,\n now,\n NOTICE_BATCH_SIZE,\n afterId,\n );\n for (const grant of grants) {\n if (!grant.expiresAt) {\n continue;\n }\n const claimed = await entitlements.claimNotice(\n orgId,\n { kind: 'expired', entitlementId: grant.id, expiresAt: grant.expiresAt },\n now,\n );\n if (claimed) {\n events.push(entitlementEvents.entitlementExpired.make({ orgId, data: grant }));\n }\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n return { events: events.length, last: grants.at(-1)?.id, size: grants.length };\n });\n appended += batch.events;\n if (batch.size < NOTICE_BATCH_SIZE || batch.last === undefined) {\n break;\n }\n afterId = batch.last;\n }\n let afterKey: EndingGrantKey | undefined;\n for (;;) {\n const batch = await this.uow.run(async ({ entitlements, outbox }) => {\n const events: NewDomainEvent[] = [];\n const ending = await entitlements.listGrantsEndingBetween(\n orgId,\n now,\n until,\n NOTICE_BATCH_SIZE,\n afterKey,\n );\n for (const { grant, communityId } of ending) {\n if (!grant.expiresAt) {\n continue;\n }\n const claimed = await entitlements.claimNotice(\n orgId,\n {\n kind: 'ending_soon',\n entitlementId: grant.id,\n communityId,\n expiresAt: grant.expiresAt,\n },\n now,\n );\n if (claimed) {\n events.push(\n entitlementEvents.entitlementEndingSoon.make({\n orgId,\n data: { ...grant, communityId },\n }),\n );\n }\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n const last = ending.at(-1);\n return {\n events: events.length,\n last: last && { entitlementId: last.grant.id, communityId: last.communityId },\n size: ending.length,\n };\n });\n appended += batch.events;\n if (batch.size < NOTICE_BATCH_SIZE || batch.last === undefined) {\n break;\n }\n afterKey = batch.last;\n }\n if (appended > 0) {\n this.logger.info('due grants expired', { orgId, events: appended });\n }\n }\n\n private async requireSpaceIn(orgId: string, communityId: string, spaceId: string) {\n const space = await this.content.getSpace(orgId, spaceId);\n if (!space || space.communityId !== communityId) {\n this.logger.warn('space members rejected — space not found', {\n orgId,\n communityId,\n spaceId,\n });\n throw new NotFoundError('Space', spaceId);\n }\n return space;\n }\n\n private async requireVisibleSpaceIn(\n orgId: string,\n actor: Actor,\n communityId: string,\n spaceId: string,\n ) {\n const space = await this.requireSpaceIn(orgId, communityId, spaceId);\n if (seesSpace(actor.role, space.accessRule, undefined)) {\n return space;\n }\n const row = await this.repo.findSpaceMember(orgId, spaceId, actor.id);\n if (!seesSpace(actor.role, space.accessRule, row?.status)) {\n throw new NotFoundError('Space', spaceId);\n }\n return space;\n }\n\n private assertHasMembers(accessRule: SpaceAccessRule): void {\n if (accessRule === 'all_members') {\n throw new ConflictError('An all-members space has no space members');\n }\n }\n\n private assertRule(accessRule: SpaceAccessRule, expected: 'by_request' | 'by_invite'): void {\n this.assertHasMembers(accessRule);\n if (accessRule !== expected) {\n throw new ConflictError(\n accessRule === 'by_invite' ? 'This space is by invitation' : 'This space is by request',\n );\n }\n }\n\n private assertHolder(actor: Actor): void {\n if (isStaffRole(actor.role)) {\n throw new ForbiddenError('Staff do not hold a place in a space');\n }\n }\n\n private async assertGrant(\n orgId: string,\n actor: Actor,\n orgUserId: string,\n communityId: string,\n ): Promise<void> {\n const member = await this.repo.hasCommunityAccess(orgId, orgUserId, communityId);\n if (!member) {\n throw new ForbiddenError(\n orgUserId === actor.id\n ? 'You are not a member of this community'\n : 'That person is not a member of this community',\n );\n }\n }\n\n private async assertHolds(\n orgId: string,\n actor: Actor,\n orgUserId: string,\n communityId: string,\n ): Promise<void> {\n const facts = await this.accessOf(orgId, orgUserId, communityId, null);\n if (facts && isStaffRole(facts.role)) {\n throw new ForbiddenError('Staff do not hold a place in a space');\n }\n await this.assertGrant(orgId, actor, orgUserId, communityId);\n }\n\n private async assertCanManage(orgId: string, actor: Actor, communityId: string): Promise<void> {\n const facts = await this.accessOf(orgId, actor.id, communityId, null);\n if (facts?.member && canModerate(facts.role)) {\n return;\n }\n throw new ForbiddenError('You cannot manage members of this space');\n }\n\n private async accessOf(\n orgId: string,\n orgUserId: string,\n communityId: string,\n spaceId: string | null,\n ): Promise<AccessFacts | null> {\n const [facts] = await this.repo.accessAmong(orgId, communityId, spaceId, [orgUserId]);\n return facts ?? null;\n }\n\n private async createMember(\n orgId: string,\n actor: Actor,\n space: { communityId: string; id: string },\n orgUserId: string,\n status: InsertSpaceMemberInput['status'],\n source: InsertSpaceMemberInput['source'],\n ): Promise<SpaceMember> {\n return this.uow.run(async ({ entitlements, outbox }) =>\n this.insertMember(entitlements, outbox, orgId, actor, {\n communityId: space.communityId,\n spaceId: space.id,\n orgUserId,\n status,\n source,\n }),\n );\n }\n\n private async insertMember(\n entitlements: EntitlementsRepository,\n outbox: OutboxAppender,\n orgId: string,\n actor: Actor,\n input: InsertSpaceMemberInput,\n room?: Space,\n ): Promise<SpaceMember> {\n const existing = await entitlements.lockSpaceMember(orgId, input.spaceId, input.orgUserId);\n if (existing && existing.status !== 'revoked') {\n throw new ConflictError(\n input.orgUserId === actor.id\n ? 'You already have a place in this space'\n : 'That person already has a place in this space',\n );\n }\n if (room) {\n await this.assertRoom(entitlements, orgId, room);\n }\n if (existing) {\n await entitlements.deleteSpaceMember(orgId, existing.id);\n }\n const created = await entitlements.insertSpaceMember(orgId, input);\n await outbox.append([\n entitlementEvents.spaceMemberCreated.make({\n orgId,\n data: created,\n metadata: { actorOrgUserId: actor.id },\n }),\n ]);\n return created;\n }\n\n private async activate(\n orgId: string,\n actor: Actor,\n spaceId: string,\n orgUserId: string,\n rule: 'by_request' | 'by_invite',\n from: SpaceMember['status'],\n missing: () => Error,\n ): Promise<SpaceMember> {\n const updated = await this.uow.run(async ({ entitlements, spaces, outbox }) => {\n const locked = await this.lockSpace(spaces, orgId, spaceId);\n this.assertRule(locked.accessRule, rule);\n const row = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (row?.status === 'active') {\n throw alreadyPlaced(actor, orgUserId);\n }\n if (!row || row.status !== from) {\n this.logger.warn('space member activate rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw missing();\n }\n await this.assertRoom(entitlements, orgId, locked);\n const next = await entitlements.updateSpaceMemberStatus(orgId, row.id, 'active');\n if (!next) {\n throw missing();\n }\n await outbox.append([\n entitlementEvents.spaceMemberUpdated.make({\n orgId,\n data: next,\n metadata: { actorOrgUserId: actor.id, previousStatus: from },\n }),\n ]);\n return next;\n });\n this.logger.info('space member activated', { orgId, spaceId, orgUserId, actorId: actor.id });\n return updated;\n }\n\n private async deleteMember(\n orgId: string,\n actor: Actor,\n spaceId: string,\n orgUserId: string,\n from: SpaceMember['status'],\n missing: () => Error,\n ): Promise<SpaceMember> {\n const row = await this.uow.run(async ({ entitlements, outbox }) => {\n const current = await entitlements.lockSpaceMember(orgId, spaceId, orgUserId);\n if (from !== 'active' && current?.status === 'active') {\n throw alreadyPlaced(actor, orgUserId);\n }\n if (!current || current.status !== from) {\n this.logger.warn('space member delete rejected — not found', {\n orgId,\n spaceId,\n orgUserId,\n actorId: actor.id,\n });\n throw missing();\n }\n await entitlements.deleteSpaceMember(orgId, current.id);\n await outbox.append([\n entitlementEvents.spaceMemberDeleted.make({\n orgId,\n data: current,\n metadata: { actorOrgUserId: actor.id, previousStatus: from },\n }),\n ]);\n return current;\n });\n this.logger.info('space member deleted', {\n orgId,\n spaceId,\n orgUserId,\n previousStatus: from,\n actorId: actor.id,\n });\n return row;\n }\n\n private async lockSpace(spaces: SpaceRowLock, orgId: string, spaceId: string) {\n const locked = await spaces.lockSpace(orgId, spaceId);\n if (!locked) {\n throw new NotFoundError('Space', spaceId);\n }\n return locked;\n }\n\n private async assertRoom(\n entitlements: EntitlementsRepository,\n orgId: string,\n space: Space,\n ): Promise<void> {\n if (space.capacity === null) {\n return;\n }\n const count = await entitlements.countActiveSpaceMembers(orgId, space.id);\n if (count >= space.capacity) {\n throw new ConflictError('This space is full');\n }\n }\n}\n\nfunction alreadyPlaced(actor: Actor, orgUserId: string): ConflictError {\n return new ConflictError(\n orgUserId === actor.id\n ? 'You already have a place in this space'\n : 'That person already has a place in this space',\n );\n}\n\nfunction ownRowMissing(status: 'invited' | 'active'): ConflictError {\n return new ConflictError(\n status === 'invited'\n ? 'You do not have an invitation to this space'\n : 'You are not a member of this space',\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","names":[],"sources":["../../src/identity/access.ts"],"sourcesContent":["export const ORG_CLAIM = 'org_id';\nexport const ACCESS_TOKEN_TTL_SECONDS = 900;\n"],"mappings":";AAAA,MAAa,YAAY;AACzB,MAAa,2BAA2B"}
|
package/dist/identity/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { CreateUserInput, ProvisionUserInput, UpdateUserInput } from "../types/schemas/identity.js";
|
|
1
|
+
import { CreateUserInput, Notification, NotificationKind, NotificationPreference, ProvisionUserInput, UpdateUserInput, UserProfile, UserProfileView } from "../types/schemas/identity.js";
|
|
2
2
|
import { User, UserId } from "../types/identity.js";
|
|
3
|
-
import { IdentityEvent, UserCreated, UserDeleted, UserUpdated, identityEvents } from "./events.js";
|
|
4
3
|
import "./model.js";
|
|
5
4
|
import "./types.js";
|
|
6
|
-
import { AuthHeaders, IdentityRepository, IdentityService, SessionAdmin, UserEditor, UserProvisioner, UserResolver } from "./ports.js";
|
|
5
|
+
import { AuthHeaders, IdentityRepository, IdentityService, NewNotification, NotificationDesk, NotificationInbox, NotificationPage, NotificationService, NotificationsQuery, NotificationsRepository, ProfilePatch, SessionAdmin, UpdateProfileInput, UserEditor, UserProvisioner, UserResolver } from "./ports.js";
|
|
7
6
|
import { IdentityServiceImpl } from "./service.js";
|
|
8
|
-
|
|
7
|
+
import { NotificationServiceImpl, defaultPreference } from "./notifications.js";
|
|
8
|
+
import { IdentityEvent, UserCreated, UserDeleted, UserUpdated, identityEvents } from "./events.js";
|
|
9
|
+
import { ACCESS_TOKEN_TTL_SECONDS, ORG_CLAIM } from "./access.js";
|
|
10
|
+
export { ACCESS_TOKEN_TTL_SECONDS, type AuthHeaders, type CreateUserInput, type IdentityEvent, type IdentityRepository, type IdentityService, IdentityServiceImpl, type NewNotification, type Notification, type NotificationDesk, type NotificationInbox, type NotificationKind, type NotificationPage, type NotificationPreference, type NotificationService, NotificationServiceImpl, type NotificationsQuery, type NotificationsRepository, ORG_CLAIM, type ProfilePatch, type ProvisionUserInput, type SessionAdmin, type UpdateProfileInput, type UpdateUserInput, type User, type UserCreated, type UserDeleted, type UserEditor, type UserId, type UserProfile, type UserProfileView, type UserProvisioner, type UserResolver, type UserUpdated, defaultPreference, identityEvents };
|
package/dist/identity/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { identityEvents } from "./events.js";
|
|
2
2
|
import { IdentityServiceImpl } from "./service.js";
|
|
3
|
-
|
|
3
|
+
import { NotificationServiceImpl, defaultPreference } from "./notifications.js";
|
|
4
|
+
import { ACCESS_TOKEN_TTL_SECONDS, ORG_CLAIM } from "./access.js";
|
|
5
|
+
export { ACCESS_TOKEN_TTL_SECONDS, IdentityServiceImpl, NotificationServiceImpl, ORG_CLAIM, defaultPreference, identityEvents };
|
package/dist/identity/model.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Notification, NotificationKind, NotificationPreference, UserProfile, UserProfileView } from "../types/schemas/identity.js";
|
|
1
2
|
import { User } from "../types/identity.js";
|
|
2
3
|
import "../types/index.js";
|
|
3
|
-
export type { User };
|
|
4
|
+
export type { Notification, NotificationKind, NotificationPreference, User, UserProfile, UserProfileView };
|