@house-rules/party 0.0.0-stage → 0.1.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/AGENTS.md +67 -0
- package/LICENSE +21 -0
- package/README.md +142 -2
- package/dist/adapters/memory/party.d.ts +3 -0
- package/dist/adapters/memory/party.js +170 -0
- package/dist/adapters/memory/person.d.ts +3 -0
- package/dist/adapters/memory/person.js +87 -0
- package/dist/adapters/postgres/party.d.ts +4 -0
- package/dist/adapters/postgres/party.js +73 -0
- package/dist/adapters/postgres/person.d.ts +4 -0
- package/dist/adapters/postgres/person.js +63 -0
- package/dist/assignment/assignment.d.ts +13 -0
- package/dist/assignment/assignment.js +9 -0
- package/dist/assignment/holders.d.ts +11 -0
- package/dist/assignment/holders.js +7 -0
- package/dist/contract.d.ts +9 -0
- package/dist/contract.js +9 -0
- package/dist/facade.d.ts +22 -0
- package/dist/facade.js +35 -0
- package/dist/index.d.ts +294 -0
- package/dist/index.js +20 -0
- package/dist/invitation/invitation.d.ts +36 -0
- package/dist/invitation/invitation.js +17 -0
- package/dist/invitation/invitations.d.ts +34 -0
- package/dist/invitation/invitations.js +70 -0
- package/dist/organisation/organisation.d.ts +59 -0
- package/dist/organisation/organisation.js +46 -0
- package/dist/person/bind-party.d.ts +7 -0
- package/dist/person/bind-party.js +42 -0
- package/dist/person/operations.d.ts +306 -0
- package/dist/person/operations.js +92 -0
- package/dist/person/party.d.ts +36 -0
- package/dist/person/party.js +55 -0
- package/dist/person/permissions.d.ts +6 -0
- package/dist/person/permissions.js +5 -0
- package/dist/person/person-store.d.ts +21 -0
- package/dist/person/person-store.js +3 -0
- package/dist/person/person.d.ts +43 -0
- package/dist/person/person.js +25 -0
- package/dist/person/policy.d.ts +1 -0
- package/dist/person/policy.js +6 -0
- package/dist/person/principal-operations.d.ts +110 -0
- package/dist/person/principal-operations.js +77 -0
- package/dist/person/principal.d.ts +126 -0
- package/dist/person/principal.js +49 -0
- package/dist/person/principals.d.ts +102 -0
- package/dist/person/principals.js +73 -0
- package/dist/relationship/relationship.d.ts +30 -0
- package/dist/relationship/relationship.js +12 -0
- package/dist/relationship/removal.d.ts +101 -0
- package/dist/relationship/removal.js +22 -0
- package/dist/role/access.d.ts +171 -0
- package/dist/role/access.js +252 -0
- package/dist/role/operations.d.ts +145 -0
- package/dist/role/operations.js +215 -0
- package/dist/role/people.d.ts +45 -0
- package/dist/role/people.js +57 -0
- package/dist/role/permission-catalogue.d.ts +2 -0
- package/dist/role/permission-catalogue.js +1 -0
- package/dist/role/role.d.ts +31 -0
- package/dist/role/role.js +12 -0
- package/dist/storage.d.ts +38 -0
- package/dist/storage.js +4 -0
- package/migrations/0001_party_person.sql +5 -0
- package/migrations/0002_party_model.sql +27 -0
- package/migrations/0003_party_invitation.sql +11 -0
- package/migrations/0004_publish_staff.sql +4 -0
- package/migrations/0005_principals.sql +18 -0
- package/package.json +57 -3
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { UnitOfWork, } from "@house-rules/domain";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { makePeople } from "./people.js";
|
|
4
|
+
import { makeHolders } from "../assignment/holders.js";
|
|
5
|
+
import { makeInvitations } from "../invitation/invitations.js";
|
|
6
|
+
import { validateAssignment } from "../assignment/assignment.js";
|
|
7
|
+
import { PartyConflict, PartyDenied, PartyInputInvalid, PartyNotFound, PartyUnavailable, validateOrganisation, } from "../organisation/organisation.js";
|
|
8
|
+
import { validateRelationship, } from "../relationship/relationship.js";
|
|
9
|
+
import { removeStaffAssignments } from "../relationship/removal.js";
|
|
10
|
+
import { validateRole } from "./role.js";
|
|
11
|
+
export const makeAccess = (store, persons, options, catalogue) => {
|
|
12
|
+
const builtIns = Object.entries(options.builtInRoles).map(([key, role]) => ({
|
|
13
|
+
key,
|
|
14
|
+
name: role.name,
|
|
15
|
+
permissions: [...role.permissions],
|
|
16
|
+
builtIn: true,
|
|
17
|
+
organisationId: null,
|
|
18
|
+
}));
|
|
19
|
+
const role = (key) => {
|
|
20
|
+
const builtIn = builtIns.find((r) => r.key === key);
|
|
21
|
+
return builtIn ? Effect.succeed(builtIn) : store.role(key);
|
|
22
|
+
};
|
|
23
|
+
const permissionsOf = Effect.fn("Party.permissionsOf")(function* (caller) {
|
|
24
|
+
const principal = yield* persons.principal(caller.actor.id);
|
|
25
|
+
if (principal && principal.disabledAt !== null)
|
|
26
|
+
return [];
|
|
27
|
+
const allowed = catalogue ? new Set(yield* catalogue.permissions()) : undefined;
|
|
28
|
+
const permissions = new Set();
|
|
29
|
+
for (const assignment of yield* store.assignments(caller.actor.id)) {
|
|
30
|
+
const held = yield* role(assignment.roleKey);
|
|
31
|
+
if (!held ||
|
|
32
|
+
(held.organisationId !== null && held.organisationId !== assignment.organisationId))
|
|
33
|
+
continue;
|
|
34
|
+
for (const p of held.permissions)
|
|
35
|
+
if (!allowed || allowed.has(p))
|
|
36
|
+
permissions.add(p);
|
|
37
|
+
}
|
|
38
|
+
return [...permissions].sort();
|
|
39
|
+
});
|
|
40
|
+
const admin = Effect.fn("Party.admin")(function* (caller, organisationId) {
|
|
41
|
+
if (caller.kind !== "person")
|
|
42
|
+
return yield* new PartyDenied();
|
|
43
|
+
const allowed = catalogue ? new Set(yield* catalogue.permissions()) : undefined;
|
|
44
|
+
if (allowed && !allowed.has("party:admin"))
|
|
45
|
+
return yield* new PartyDenied();
|
|
46
|
+
for (const assignment of yield* store.assignments(caller.actor.id)) {
|
|
47
|
+
const held = yield* role(assignment.roleKey);
|
|
48
|
+
if (held &&
|
|
49
|
+
(held.organisationId === null || held.organisationId === assignment.organisationId) &&
|
|
50
|
+
held.permissions.includes("party:admin") &&
|
|
51
|
+
(assignment.organisationId === null || assignment.organisationId === organisationId))
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
return yield* new PartyDenied();
|
|
55
|
+
});
|
|
56
|
+
const mayDelegate = Effect.fn("Party.mayDelegate")(function* (caller, permissions, organisationId) {
|
|
57
|
+
if (caller.kind === "system")
|
|
58
|
+
return;
|
|
59
|
+
const held = new Set();
|
|
60
|
+
for (const assignment of yield* store.assignments(caller.actor.id)) {
|
|
61
|
+
if (assignment.organisationId !== null && assignment.organisationId !== organisationId)
|
|
62
|
+
continue;
|
|
63
|
+
const assigned = yield* role(assignment.roleKey);
|
|
64
|
+
if (assigned &&
|
|
65
|
+
(assigned.organisationId === null || assigned.organisationId === assignment.organisationId))
|
|
66
|
+
for (const permission of assigned.permissions)
|
|
67
|
+
held.add(permission);
|
|
68
|
+
}
|
|
69
|
+
if (permissions.some((permission) => !held.has(permission)))
|
|
70
|
+
return yield* new PartyDenied();
|
|
71
|
+
});
|
|
72
|
+
const visibleScopes = Effect.fn("Party.visibleScopes")(function* (caller) {
|
|
73
|
+
if (caller.kind === "system")
|
|
74
|
+
return null;
|
|
75
|
+
const scopes = new Set();
|
|
76
|
+
for (const assignment of yield* store.assignments(caller.actor.id)) {
|
|
77
|
+
const held = yield* role(assignment.roleKey);
|
|
78
|
+
if (!held ||
|
|
79
|
+
(held.organisationId !== null && held.organisationId !== assignment.organisationId))
|
|
80
|
+
continue;
|
|
81
|
+
if (assignment.organisationId === null) {
|
|
82
|
+
if (held.permissions.includes("party:admin"))
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
scopes.add(assignment.organisationId);
|
|
87
|
+
}
|
|
88
|
+
return [...scopes];
|
|
89
|
+
});
|
|
90
|
+
const organisationExists = Effect.fn("Party.organisationExists")(function* (id) {
|
|
91
|
+
if (id !== null && !(yield* store.organisation(id)))
|
|
92
|
+
return yield* new PartyNotFound();
|
|
93
|
+
});
|
|
94
|
+
const partyExists = Effect.fn("Party.partyExists")(function* (ref) {
|
|
95
|
+
const found = ref.kind === "person"
|
|
96
|
+
? yield* persons.find(ref.id)
|
|
97
|
+
: yield* store.organisation(ref.id);
|
|
98
|
+
if (!found)
|
|
99
|
+
return yield* new PartyNotFound();
|
|
100
|
+
});
|
|
101
|
+
const relationScope = Effect.fn("Party.relationScope")(function* (caller, r) {
|
|
102
|
+
// A scoped admin must control every organisation in a relation, not merely one endpoint.
|
|
103
|
+
const organisations = [r.from, r.to].filter((p) => p.kind === "organisation");
|
|
104
|
+
if (organisations.length === 0)
|
|
105
|
+
yield* admin(caller, null);
|
|
106
|
+
for (const org of organisations)
|
|
107
|
+
yield* admin(caller, org.id);
|
|
108
|
+
});
|
|
109
|
+
const adminScopes = Effect.fn("Party.adminScopes")(function* (caller) {
|
|
110
|
+
if (caller.kind !== "person")
|
|
111
|
+
return yield* new PartyDenied();
|
|
112
|
+
const scopes = new Set();
|
|
113
|
+
if (catalogue && !(yield* catalogue.permissions()).includes("party:admin"))
|
|
114
|
+
return yield* new PartyDenied();
|
|
115
|
+
for (const assignment of yield* store.assignments(caller.actor.id)) {
|
|
116
|
+
const held = yield* role(assignment.roleKey);
|
|
117
|
+
if (!held ||
|
|
118
|
+
!held.permissions.includes("party:admin") ||
|
|
119
|
+
(held.organisationId !== null && held.organisationId !== assignment.organisationId))
|
|
120
|
+
continue;
|
|
121
|
+
if (assignment.organisationId === null)
|
|
122
|
+
return null;
|
|
123
|
+
scopes.add(assignment.organisationId);
|
|
124
|
+
}
|
|
125
|
+
if (scopes.size === 0)
|
|
126
|
+
return yield* new PartyDenied();
|
|
127
|
+
return [...scopes];
|
|
128
|
+
});
|
|
129
|
+
const checkInvitationRole = Effect.fn("Party.checkInvitationRole")(function* (caller, key, org) {
|
|
130
|
+
yield* admin(caller, org);
|
|
131
|
+
const held = yield* role(key);
|
|
132
|
+
if (!held)
|
|
133
|
+
return yield* new PartyNotFound();
|
|
134
|
+
if (held.organisationId !== null && held.organisationId !== org)
|
|
135
|
+
return yield* new PartyDenied();
|
|
136
|
+
yield* mayDelegate(caller, held.permissions, org);
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
...makeInvitations(store, admin, checkInvitationRole),
|
|
140
|
+
...makePeople(store, persons, role, adminScopes, organisationExists),
|
|
141
|
+
permissionsOf,
|
|
142
|
+
...makeHolders(store, options.holdersLimit ?? 1000),
|
|
143
|
+
canAdminOrganisation: Effect.fn("Party.canAdminOrganisation")(function* (caller, organisationId) {
|
|
144
|
+
if (!(yield* store.organisation(organisationId)))
|
|
145
|
+
return false;
|
|
146
|
+
return yield* admin(caller, organisationId).pipe(Effect.as(true), Effect.catchTag("PartyDenied", () => Effect.succeed(false)));
|
|
147
|
+
}),
|
|
148
|
+
myAccess: Effect.fn("Party.myAccess")(function* (caller) {
|
|
149
|
+
if (caller.kind !== "person")
|
|
150
|
+
return yield* new PartyDenied();
|
|
151
|
+
return {
|
|
152
|
+
roles: yield* store.assignments(caller.actor.id),
|
|
153
|
+
permissions: yield* permissionsOf(caller),
|
|
154
|
+
catalogue: catalogue ? [...(yield* catalogue.permissions())].sort() : [],
|
|
155
|
+
};
|
|
156
|
+
}),
|
|
157
|
+
createOrganisation: Effect.fn("Party.createOrganisation")(function* (caller, input) {
|
|
158
|
+
yield* UnitOfWork.required;
|
|
159
|
+
if (caller.kind !== "person")
|
|
160
|
+
return yield* new PartyDenied();
|
|
161
|
+
const value = yield* validateOrganisation(input);
|
|
162
|
+
if (!(yield* store.insertOrganisation(value)))
|
|
163
|
+
return yield* new PartyConflict();
|
|
164
|
+
yield* store.relate({
|
|
165
|
+
from: { kind: "person", id: caller.actor.id },
|
|
166
|
+
to: { kind: "organisation", id: value.id },
|
|
167
|
+
kind: "staff",
|
|
168
|
+
});
|
|
169
|
+
return value;
|
|
170
|
+
}),
|
|
171
|
+
showOrganisation: Effect.fn("Party.showOrganisation")(function* (id) {
|
|
172
|
+
const value = yield* store.organisation(id);
|
|
173
|
+
if (!value)
|
|
174
|
+
return yield* new PartyNotFound();
|
|
175
|
+
return value;
|
|
176
|
+
}),
|
|
177
|
+
relate: Effect.fn("Party.relate")(function* (caller, input) {
|
|
178
|
+
yield* UnitOfWork.required;
|
|
179
|
+
const value = yield* validateRelationship(input);
|
|
180
|
+
yield* relationScope(caller, value);
|
|
181
|
+
yield* partyExists(value.from);
|
|
182
|
+
yield* partyExists(value.to);
|
|
183
|
+
yield* store.relate(value);
|
|
184
|
+
}),
|
|
185
|
+
unrelate: Effect.fn("Party.unrelate")(function* (caller, input) {
|
|
186
|
+
yield* UnitOfWork.required;
|
|
187
|
+
const value = yield* validateRelationship(input);
|
|
188
|
+
yield* relationScope(caller, value);
|
|
189
|
+
yield* removeStaffAssignments(store, role, value);
|
|
190
|
+
yield* store.unrelate(value);
|
|
191
|
+
}),
|
|
192
|
+
isRelated: (input) => validateRelationship(input).pipe(Effect.flatMap(store.isRelated)),
|
|
193
|
+
defineRole: Effect.fn("Party.defineRole")(function* (caller, input) {
|
|
194
|
+
yield* UnitOfWork.required;
|
|
195
|
+
const value = yield* validateRole({ ...input, builtIn: false });
|
|
196
|
+
yield* admin(caller, value.organisationId);
|
|
197
|
+
if (builtIns.some((r) => r.key === value.key))
|
|
198
|
+
return yield* new PartyConflict();
|
|
199
|
+
const previous = yield* store.role(value.key);
|
|
200
|
+
if (previous)
|
|
201
|
+
yield* admin(caller, previous.organisationId);
|
|
202
|
+
yield* organisationExists(value.organisationId);
|
|
203
|
+
if (!catalogue)
|
|
204
|
+
return yield* new PartyUnavailable({ cause: "Permission catalogue not mounted" });
|
|
205
|
+
const allowed = new Set(yield* catalogue.permissions());
|
|
206
|
+
if (value.permissions.some((p) => !allowed.has(p)))
|
|
207
|
+
return yield* new PartyInputInvalid({ message: "Unknown role permission." });
|
|
208
|
+
yield* mayDelegate(caller, value.permissions, value.organisationId);
|
|
209
|
+
yield* store.putRole(value);
|
|
210
|
+
return value;
|
|
211
|
+
}),
|
|
212
|
+
deleteRole: Effect.fn("Party.deleteRole")(function* (caller, key) {
|
|
213
|
+
yield* UnitOfWork.required;
|
|
214
|
+
const value = yield* role(key);
|
|
215
|
+
if (!value)
|
|
216
|
+
return yield* new PartyNotFound();
|
|
217
|
+
yield* admin(caller, value.organisationId);
|
|
218
|
+
if (value.builtIn)
|
|
219
|
+
return yield* new PartyConflict();
|
|
220
|
+
yield* store.deleteRole(key);
|
|
221
|
+
}),
|
|
222
|
+
listRoles: Effect.fn("Party.listRoles")(function* (caller) {
|
|
223
|
+
return [...builtIns, ...(yield* store.roles(yield* visibleScopes(caller)))];
|
|
224
|
+
}),
|
|
225
|
+
assignRole: Effect.fn("Party.assignRole")(function* (caller, input) {
|
|
226
|
+
yield* UnitOfWork.required;
|
|
227
|
+
const value = yield* validateAssignment(input);
|
|
228
|
+
// Trusted first sign-in can assign a default role without person authority.
|
|
229
|
+
if (caller.kind !== "system")
|
|
230
|
+
yield* admin(caller, value.organisationId);
|
|
231
|
+
yield* organisationExists(value.organisationId);
|
|
232
|
+
if (!(yield* persons.find(value.personId)))
|
|
233
|
+
return yield* new PartyNotFound();
|
|
234
|
+
const held = yield* role(value.roleKey);
|
|
235
|
+
if (!held)
|
|
236
|
+
return yield* new PartyNotFound();
|
|
237
|
+
if (held.organisationId !== null && held.organisationId !== value.organisationId)
|
|
238
|
+
return yield* new PartyDenied();
|
|
239
|
+
yield* mayDelegate(caller, held.permissions, value.organisationId);
|
|
240
|
+
yield* store.assign(value);
|
|
241
|
+
}),
|
|
242
|
+
revokeRole: Effect.fn("Party.revokeRole")(function* (caller, input) {
|
|
243
|
+
yield* UnitOfWork.required;
|
|
244
|
+
const value = yield* validateAssignment(input);
|
|
245
|
+
yield* admin(caller, value.organisationId);
|
|
246
|
+
yield* store.revoke(value);
|
|
247
|
+
}),
|
|
248
|
+
rolesOf: Effect.fn("Party.rolesOf")(function* (caller, personId) {
|
|
249
|
+
return yield* store.assignments(personId, yield* visibleScopes(caller), builtIns.map((r) => r.key));
|
|
250
|
+
}),
|
|
251
|
+
};
|
|
252
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { PartyLastAdmin, PartyConflict, PartyDenied, PartyInputInvalid, PartyNotFound, PartyUnavailable } from "../organisation/organisation.js";
|
|
3
|
+
export declare const AccessOperations: {
|
|
4
|
+
readonly canAdminOrganisation: import("@house-rules/domain/contract").Operation<"Party.canAdminOrganisation", Schema.Struct<{
|
|
5
|
+
readonly organisationId: Schema.String;
|
|
6
|
+
}>, Schema.Boolean, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
|
|
7
|
+
readonly createInvitation: import("@house-rules/domain/contract").Operation<"Party.createInvitation", Schema.Struct<{
|
|
8
|
+
readonly organisationId: Schema.String;
|
|
9
|
+
readonly roleKey: Schema.NullOr<Schema.String>;
|
|
10
|
+
}>, Schema.Struct<{
|
|
11
|
+
readonly invitation: Schema.Struct<{
|
|
12
|
+
readonly id: Schema.String;
|
|
13
|
+
readonly organisationId: Schema.String;
|
|
14
|
+
readonly roleKey: Schema.NullOr<Schema.String>;
|
|
15
|
+
readonly createdBy: Schema.brand<Schema.NonEmptyString, "ActorId">;
|
|
16
|
+
readonly expiresAt: Schema.Finite;
|
|
17
|
+
readonly state: Schema.Literals<readonly ["pending", "used", "revoked"]>;
|
|
18
|
+
}>;
|
|
19
|
+
readonly code: Schema.String;
|
|
20
|
+
}>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, false, true, false>;
|
|
21
|
+
readonly acceptInvitation: import("@house-rules/domain/contract").Operation<"Party.acceptInvitation", Schema.Struct<{
|
|
22
|
+
readonly code: Schema.String;
|
|
23
|
+
}>, Schema.Struct<{
|
|
24
|
+
readonly organisationId: Schema.String;
|
|
25
|
+
readonly roleKey: Schema.NullOr<Schema.String>;
|
|
26
|
+
readonly personId: Schema.brand<Schema.NonEmptyString, "ActorId">;
|
|
27
|
+
}>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, false, true, false>;
|
|
28
|
+
readonly revokeInvitation: import("@house-rules/domain/contract").Operation<"Party.revokeInvitation", Schema.Struct<{
|
|
29
|
+
readonly id: Schema.String;
|
|
30
|
+
}>, Schema.Void, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, true, true, true>;
|
|
31
|
+
readonly listInvitations: import("@house-rules/domain/contract").Operation<"Party.listInvitations", Schema.Struct<{
|
|
32
|
+
readonly organisationId: Schema.String;
|
|
33
|
+
}>, Schema.$Array<Schema.Struct<{
|
|
34
|
+
readonly id: Schema.String;
|
|
35
|
+
readonly organisationId: Schema.String;
|
|
36
|
+
readonly roleKey: Schema.NullOr<Schema.String>;
|
|
37
|
+
readonly createdBy: Schema.brand<Schema.NonEmptyString, "ActorId">;
|
|
38
|
+
readonly expiresAt: Schema.Finite;
|
|
39
|
+
readonly state: Schema.Literals<readonly ["pending", "used", "revoked"]>;
|
|
40
|
+
}>>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
|
|
41
|
+
readonly myOrganisations: import("@house-rules/domain/contract").Operation<"Party.myOrganisations", Schema.$Record<Schema.String, Schema.Never>, Schema.$Array<Schema.Struct<{
|
|
42
|
+
readonly id: Schema.NonEmptyString;
|
|
43
|
+
readonly name: Schema.NonEmptyString;
|
|
44
|
+
readonly relation: Schema.NullOr<Schema.String>;
|
|
45
|
+
readonly roles: Schema.$Array<Schema.Struct<{
|
|
46
|
+
readonly roleKey: Schema.String;
|
|
47
|
+
readonly roleName: Schema.String;
|
|
48
|
+
readonly permissions: Schema.$Array<Schema.String>;
|
|
49
|
+
}>>;
|
|
50
|
+
}>>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
|
|
51
|
+
readonly myAccess: import("@house-rules/domain/contract").Operation<"Party.myAccess", Schema.$Record<Schema.String, Schema.Never>, Schema.Struct<{
|
|
52
|
+
readonly roles: Schema.$Array<Schema.Struct<{
|
|
53
|
+
readonly personId: Schema.brand<Schema.NonEmptyString, "ActorId">;
|
|
54
|
+
readonly roleKey: Schema.NonEmptyString;
|
|
55
|
+
readonly organisationId: Schema.NullOr<Schema.NonEmptyString>;
|
|
56
|
+
}>>;
|
|
57
|
+
readonly permissions: Schema.$Array<Schema.String>;
|
|
58
|
+
readonly catalogue: Schema.$Array<Schema.String>;
|
|
59
|
+
}>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
|
|
60
|
+
readonly createOrganisation: import("@house-rules/domain/contract").Operation<"Party.createOrganisation", Schema.Struct<{
|
|
61
|
+
readonly id: Schema.NonEmptyString;
|
|
62
|
+
readonly name: Schema.NonEmptyString;
|
|
63
|
+
}>, Schema.Struct<{
|
|
64
|
+
readonly id: Schema.NonEmptyString;
|
|
65
|
+
readonly name: Schema.NonEmptyString;
|
|
66
|
+
}>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, false, true, false>;
|
|
67
|
+
readonly showOrganisation: import("@house-rules/domain/contract").Operation<"Party.showOrganisation", Schema.Struct<{
|
|
68
|
+
readonly id: Schema.String;
|
|
69
|
+
}>, Schema.Struct<{
|
|
70
|
+
readonly id: Schema.NonEmptyString;
|
|
71
|
+
readonly name: Schema.NonEmptyString;
|
|
72
|
+
}>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
|
|
73
|
+
readonly relate: import("@house-rules/domain/contract").Operation<"Party.relate", Schema.Struct<{
|
|
74
|
+
readonly from: Schema.Struct<{
|
|
75
|
+
readonly kind: Schema.Literals<readonly ["person", "organisation"]>;
|
|
76
|
+
readonly id: Schema.NonEmptyString;
|
|
77
|
+
}>;
|
|
78
|
+
readonly to: Schema.Struct<{
|
|
79
|
+
readonly kind: Schema.Literals<readonly ["person", "organisation"]>;
|
|
80
|
+
readonly id: Schema.NonEmptyString;
|
|
81
|
+
}>;
|
|
82
|
+
readonly kind: Schema.NonEmptyString;
|
|
83
|
+
}>, Schema.Void, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, false, true, false>;
|
|
84
|
+
readonly unrelate: import("@house-rules/domain/contract").Operation<"Party.unrelate", Schema.Struct<{
|
|
85
|
+
readonly from: Schema.Struct<{
|
|
86
|
+
readonly kind: Schema.Literals<readonly ["person", "organisation"]>;
|
|
87
|
+
readonly id: Schema.NonEmptyString;
|
|
88
|
+
}>;
|
|
89
|
+
readonly to: Schema.Struct<{
|
|
90
|
+
readonly kind: Schema.Literals<readonly ["person", "organisation"]>;
|
|
91
|
+
readonly id: Schema.NonEmptyString;
|
|
92
|
+
}>;
|
|
93
|
+
readonly kind: Schema.NonEmptyString;
|
|
94
|
+
}>, Schema.Void, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, true, true, true>;
|
|
95
|
+
readonly isRelated: import("@house-rules/domain/contract").Operation<"Party.isRelated", Schema.Struct<{
|
|
96
|
+
readonly from: Schema.Struct<{
|
|
97
|
+
readonly kind: Schema.Literals<readonly ["person", "organisation"]>;
|
|
98
|
+
readonly id: Schema.NonEmptyString;
|
|
99
|
+
}>;
|
|
100
|
+
readonly to: Schema.Struct<{
|
|
101
|
+
readonly kind: Schema.Literals<readonly ["person", "organisation"]>;
|
|
102
|
+
readonly id: Schema.NonEmptyString;
|
|
103
|
+
}>;
|
|
104
|
+
readonly kind: Schema.NonEmptyString;
|
|
105
|
+
}>, Schema.Boolean, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
|
|
106
|
+
readonly defineRole: import("@house-rules/domain/contract").Operation<"Party.defineRole", Schema.Struct<{
|
|
107
|
+
readonly key: Schema.String;
|
|
108
|
+
readonly name: Schema.String;
|
|
109
|
+
readonly permissions: Schema.$Array<Schema.String>;
|
|
110
|
+
readonly organisationId: Schema.NullOr<Schema.String>;
|
|
111
|
+
}>, Schema.Struct<{
|
|
112
|
+
readonly key: Schema.NonEmptyString;
|
|
113
|
+
readonly name: Schema.NonEmptyString;
|
|
114
|
+
readonly permissions: Schema.$Array<Schema.String>;
|
|
115
|
+
readonly organisationId: Schema.NullOr<Schema.NonEmptyString>;
|
|
116
|
+
readonly builtIn: Schema.Boolean;
|
|
117
|
+
}>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, false, true, false>;
|
|
118
|
+
readonly deleteRole: import("@house-rules/domain/contract").Operation<"Party.deleteRole", Schema.Struct<{
|
|
119
|
+
readonly key: Schema.String;
|
|
120
|
+
}>, Schema.Void, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, true, true, true>;
|
|
121
|
+
readonly listRoles: import("@house-rules/domain/contract").Operation<"Party.listRoles", Schema.$Record<Schema.String, Schema.Never>, Schema.$Array<Schema.Struct<{
|
|
122
|
+
readonly key: Schema.NonEmptyString;
|
|
123
|
+
readonly name: Schema.NonEmptyString;
|
|
124
|
+
readonly permissions: Schema.$Array<Schema.String>;
|
|
125
|
+
readonly organisationId: Schema.NullOr<Schema.NonEmptyString>;
|
|
126
|
+
readonly builtIn: Schema.Boolean;
|
|
127
|
+
}>>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
|
|
128
|
+
readonly assignRole: import("@house-rules/domain/contract").Operation<"Party.assignRole", Schema.Struct<{
|
|
129
|
+
readonly personId: Schema.brand<Schema.NonEmptyString, "ActorId">;
|
|
130
|
+
readonly roleKey: Schema.NonEmptyString;
|
|
131
|
+
readonly organisationId: Schema.NullOr<Schema.NonEmptyString>;
|
|
132
|
+
}>, Schema.Void, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, false, true, false>;
|
|
133
|
+
readonly revokeRole: import("@house-rules/domain/contract").Operation<"Party.revokeRole", Schema.Struct<{
|
|
134
|
+
readonly personId: Schema.brand<Schema.NonEmptyString, "ActorId">;
|
|
135
|
+
readonly roleKey: Schema.NonEmptyString;
|
|
136
|
+
readonly organisationId: Schema.NullOr<Schema.NonEmptyString>;
|
|
137
|
+
}>, Schema.Void, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, false, true, true, true>;
|
|
138
|
+
readonly rolesOf: import("@house-rules/domain/contract").Operation<"Party.rolesOf", Schema.Struct<{
|
|
139
|
+
readonly personId: Schema.brand<Schema.NonEmptyString, "ActorId">;
|
|
140
|
+
}>, Schema.$Array<Schema.Struct<{
|
|
141
|
+
readonly personId: Schema.brand<Schema.NonEmptyString, "ActorId">;
|
|
142
|
+
readonly roleKey: Schema.NonEmptyString;
|
|
143
|
+
readonly organisationId: Schema.NullOr<Schema.NonEmptyString>;
|
|
144
|
+
}>>, Schema.Union<readonly [typeof PartyLastAdmin, typeof PartyConflict, typeof PartyDenied, typeof PartyInputInvalid, typeof PartyNotFound, typeof PartyUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
|
|
145
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { ActorId, defineOperation, NoInput } from "@house-rules/domain/contract";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import { Assignment } from "../assignment/assignment.js";
|
|
4
|
+
import { Organisation, OwnOrganisation, PartyLastAdmin, PartyConflict, PartyDenied, PartyInputInvalid, PartyNotFound, PartyUnavailable, } from "../organisation/organisation.js";
|
|
5
|
+
import { Relationship } from "../relationship/relationship.js";
|
|
6
|
+
import { Role } from "./role.js";
|
|
7
|
+
import { CreatedInvitation, Invitation, InvitationAcceptance } from "../invitation/invitation.js";
|
|
8
|
+
const failure = Schema.Union([
|
|
9
|
+
PartyLastAdmin,
|
|
10
|
+
PartyConflict,
|
|
11
|
+
PartyDenied,
|
|
12
|
+
PartyInputInvalid,
|
|
13
|
+
PartyNotFound,
|
|
14
|
+
PartyUnavailable,
|
|
15
|
+
]);
|
|
16
|
+
const createOrganisation = defineOperation("Party.createOrganisation", {
|
|
17
|
+
description: "Create an organisation.",
|
|
18
|
+
input: Organisation,
|
|
19
|
+
output: Organisation,
|
|
20
|
+
failure,
|
|
21
|
+
permission: "party:create-organisation",
|
|
22
|
+
edges: [],
|
|
23
|
+
transactional: true,
|
|
24
|
+
});
|
|
25
|
+
const showOrganisation = defineOperation("Party.showOrganisation", {
|
|
26
|
+
description: "Show an organisation.",
|
|
27
|
+
input: Schema.Struct({ id: Schema.String }),
|
|
28
|
+
output: Organisation,
|
|
29
|
+
failure,
|
|
30
|
+
permission: "party:read",
|
|
31
|
+
edges: [],
|
|
32
|
+
readOnly: true,
|
|
33
|
+
});
|
|
34
|
+
const relate = defineOperation("Party.relate", {
|
|
35
|
+
description: "Relate two parties.",
|
|
36
|
+
input: Relationship,
|
|
37
|
+
output: Schema.Void,
|
|
38
|
+
failure,
|
|
39
|
+
permission: "party:admin",
|
|
40
|
+
edges: [],
|
|
41
|
+
transactional: true,
|
|
42
|
+
});
|
|
43
|
+
const unrelate = defineOperation("Party.unrelate", {
|
|
44
|
+
description: "Remove a relationship.",
|
|
45
|
+
input: Relationship,
|
|
46
|
+
output: Schema.Void,
|
|
47
|
+
failure,
|
|
48
|
+
permission: "party:admin",
|
|
49
|
+
edges: [],
|
|
50
|
+
transactional: true,
|
|
51
|
+
destructive: true,
|
|
52
|
+
needsApproval: true,
|
|
53
|
+
});
|
|
54
|
+
const isRelated = defineOperation("Party.isRelated", {
|
|
55
|
+
description: "Check a relationship between parties.",
|
|
56
|
+
input: Relationship,
|
|
57
|
+
output: Schema.Boolean,
|
|
58
|
+
failure,
|
|
59
|
+
permission: "party:read",
|
|
60
|
+
edges: [],
|
|
61
|
+
readOnly: true,
|
|
62
|
+
});
|
|
63
|
+
const canAdminOrganisation = defineOperation("Party.canAdminOrganisation", {
|
|
64
|
+
description: "Check your current admin permission in an organisation scope.",
|
|
65
|
+
input: Schema.Struct({ organisationId: Schema.String }),
|
|
66
|
+
output: Schema.Boolean,
|
|
67
|
+
failure,
|
|
68
|
+
permission: "party:read",
|
|
69
|
+
readOnly: true,
|
|
70
|
+
});
|
|
71
|
+
const defineRole = defineOperation("Party.defineRole", {
|
|
72
|
+
description: "Create or update a custom role.",
|
|
73
|
+
input: Schema.Struct({
|
|
74
|
+
key: Schema.String,
|
|
75
|
+
name: Schema.String,
|
|
76
|
+
permissions: Schema.Array(Schema.String),
|
|
77
|
+
organisationId: Schema.NullOr(Schema.String),
|
|
78
|
+
}),
|
|
79
|
+
output: Role,
|
|
80
|
+
failure,
|
|
81
|
+
permission: "party:admin",
|
|
82
|
+
edges: [],
|
|
83
|
+
transactional: true,
|
|
84
|
+
});
|
|
85
|
+
const deleteRole = defineOperation("Party.deleteRole", {
|
|
86
|
+
description: "Delete a custom role and its assignments.",
|
|
87
|
+
input: Schema.Struct({ key: Schema.String }),
|
|
88
|
+
output: Schema.Void,
|
|
89
|
+
failure,
|
|
90
|
+
permission: "party:admin",
|
|
91
|
+
edges: [],
|
|
92
|
+
transactional: true,
|
|
93
|
+
destructive: true,
|
|
94
|
+
needsApproval: true,
|
|
95
|
+
});
|
|
96
|
+
const listRoles = defineOperation("Party.listRoles", {
|
|
97
|
+
description: "List built-in and custom roles.",
|
|
98
|
+
input: NoInput,
|
|
99
|
+
output: Schema.Array(Role),
|
|
100
|
+
failure,
|
|
101
|
+
permission: "party:read",
|
|
102
|
+
edges: [],
|
|
103
|
+
readOnly: true,
|
|
104
|
+
});
|
|
105
|
+
const assignRole = defineOperation("Party.assignRole", {
|
|
106
|
+
description: "Assign a role as an administrator or trusted system setup.",
|
|
107
|
+
input: Assignment,
|
|
108
|
+
output: Schema.Void,
|
|
109
|
+
failure,
|
|
110
|
+
permission: "party:admin",
|
|
111
|
+
edges: [],
|
|
112
|
+
transactional: true,
|
|
113
|
+
});
|
|
114
|
+
const revokeRole = defineOperation("Party.revokeRole", {
|
|
115
|
+
description: "Revoke a role assignment.",
|
|
116
|
+
input: Assignment,
|
|
117
|
+
output: Schema.Void,
|
|
118
|
+
failure,
|
|
119
|
+
permission: "party:admin",
|
|
120
|
+
edges: [],
|
|
121
|
+
transactional: true,
|
|
122
|
+
destructive: true,
|
|
123
|
+
needsApproval: true,
|
|
124
|
+
});
|
|
125
|
+
const rolesOf = defineOperation("Party.rolesOf", {
|
|
126
|
+
description: "Read a person's role assignments.",
|
|
127
|
+
input: Schema.Struct({ personId: ActorId }),
|
|
128
|
+
output: Schema.Array(Assignment),
|
|
129
|
+
failure,
|
|
130
|
+
permission: "party:read",
|
|
131
|
+
edges: [],
|
|
132
|
+
readOnly: true,
|
|
133
|
+
});
|
|
134
|
+
const myAccess = defineOperation("Party.myAccess", {
|
|
135
|
+
description: "Read your own role assignments, effective permissions and the app catalogue.",
|
|
136
|
+
input: NoInput,
|
|
137
|
+
output: Schema.Struct({
|
|
138
|
+
roles: Schema.Array(Assignment),
|
|
139
|
+
permissions: Schema.Array(Schema.String),
|
|
140
|
+
catalogue: Schema.Array(Schema.String),
|
|
141
|
+
}),
|
|
142
|
+
failure,
|
|
143
|
+
permission: "party:read",
|
|
144
|
+
edges: [],
|
|
145
|
+
readOnly: true,
|
|
146
|
+
});
|
|
147
|
+
const createInvitation = defineOperation("Party.createInvitation", {
|
|
148
|
+
description: "Invite a person into your organisation with an optional delegated role.",
|
|
149
|
+
input: Schema.Struct({ organisationId: Schema.String, roleKey: Schema.NullOr(Schema.String) }),
|
|
150
|
+
output: CreatedInvitation,
|
|
151
|
+
failure,
|
|
152
|
+
permission: "party:admin",
|
|
153
|
+
transactional: true,
|
|
154
|
+
});
|
|
155
|
+
const acceptInvitation = defineOperation("Party.acceptInvitation", {
|
|
156
|
+
description: "Consume a one-time invitation and join as staff.",
|
|
157
|
+
input: Schema.Struct({ code: Schema.String }),
|
|
158
|
+
output: InvitationAcceptance,
|
|
159
|
+
failure,
|
|
160
|
+
permission: "party:read",
|
|
161
|
+
transactional: true,
|
|
162
|
+
});
|
|
163
|
+
const revokeInvitation = defineOperation("Party.revokeInvitation", {
|
|
164
|
+
description: "Revoke an invitation in your organisation.",
|
|
165
|
+
input: Schema.Struct({ id: Schema.String }),
|
|
166
|
+
output: Schema.Void,
|
|
167
|
+
failure,
|
|
168
|
+
permission: "party:admin",
|
|
169
|
+
transactional: true,
|
|
170
|
+
destructive: true,
|
|
171
|
+
needsApproval: true,
|
|
172
|
+
});
|
|
173
|
+
const listInvitations = defineOperation("Party.listInvitations", {
|
|
174
|
+
description: "List invitations in an organisation you administer, without codes or hashes.",
|
|
175
|
+
input: Schema.Struct({ organisationId: Schema.String }),
|
|
176
|
+
output: Schema.Array(Invitation),
|
|
177
|
+
failure,
|
|
178
|
+
permission: "party:admin",
|
|
179
|
+
readOnly: true,
|
|
180
|
+
});
|
|
181
|
+
const myOrganisations = defineOperation("Party.myOrganisations", {
|
|
182
|
+
description: "List organisations you belong to.",
|
|
183
|
+
input: NoInput,
|
|
184
|
+
output: Schema.Array(Schema.Struct({
|
|
185
|
+
...OwnOrganisation.fields,
|
|
186
|
+
roles: Schema.Array(Schema.Struct({
|
|
187
|
+
roleKey: Schema.String,
|
|
188
|
+
roleName: Schema.String,
|
|
189
|
+
permissions: Schema.Array(Schema.String),
|
|
190
|
+
})),
|
|
191
|
+
})),
|
|
192
|
+
failure,
|
|
193
|
+
permission: "party:read",
|
|
194
|
+
readOnly: true,
|
|
195
|
+
});
|
|
196
|
+
export const AccessOperations = {
|
|
197
|
+
canAdminOrganisation,
|
|
198
|
+
createInvitation,
|
|
199
|
+
acceptInvitation,
|
|
200
|
+
revokeInvitation,
|
|
201
|
+
listInvitations,
|
|
202
|
+
myOrganisations,
|
|
203
|
+
myAccess,
|
|
204
|
+
createOrganisation,
|
|
205
|
+
showOrganisation,
|
|
206
|
+
relate,
|
|
207
|
+
unrelate,
|
|
208
|
+
isRelated,
|
|
209
|
+
defineRole,
|
|
210
|
+
deleteRole,
|
|
211
|
+
listRoles,
|
|
212
|
+
assignRole,
|
|
213
|
+
revokeRole,
|
|
214
|
+
rolesOf,
|
|
215
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ActorId, Caller } from "@house-rules/domain";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { PartyDenied, PartyNotFound, type PartyUnavailable } from "../organisation/organisation.js";
|
|
4
|
+
import type { PartyStore, PersonStore } from "../storage.js";
|
|
5
|
+
import type { Role } from "./role.js";
|
|
6
|
+
export declare const makePeople: (store: PartyStore["Service"], persons: {
|
|
7
|
+
readonly list: (ids: readonly ActorId[] | null) => Effect.Effect<Effect.Success<ReturnType<PersonStore["Service"]["list"]>>, PartyUnavailable>;
|
|
8
|
+
}, role: (key: string) => Effect.Effect<Role | undefined, PartyUnavailable>, adminScopes: (caller: Caller) => Effect.Effect<string[] | null, PartyDenied | PartyUnavailable>, organisationExists: (id: string | null) => Effect.Effect<void, PartyNotFound | PartyUnavailable>) => {
|
|
9
|
+
listPeople: (caller: Caller) => Effect.Effect<{
|
|
10
|
+
id: string & import("effect/Brand").Brand<"ActorId">;
|
|
11
|
+
displayName: string;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
assignments: {
|
|
14
|
+
roleKey: string;
|
|
15
|
+
roleName: string;
|
|
16
|
+
organisationId: string | null;
|
|
17
|
+
organisationName: string | null;
|
|
18
|
+
}[];
|
|
19
|
+
organisations: readonly {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly relation: string;
|
|
23
|
+
}[];
|
|
24
|
+
}[], PartyDenied | PartyUnavailable, never>;
|
|
25
|
+
listMembers: (caller: Caller, organisationId: string) => Effect.Effect<{
|
|
26
|
+
id: string & import("effect/Brand").Brand<"ActorId">;
|
|
27
|
+
displayName: string;
|
|
28
|
+
createdAt: number;
|
|
29
|
+
relation: string | null;
|
|
30
|
+
roles: {
|
|
31
|
+
roleKey: string;
|
|
32
|
+
roleName: string;
|
|
33
|
+
}[];
|
|
34
|
+
}[], PartyNotFound | PartyUnavailable, never>;
|
|
35
|
+
myOrganisations: (caller: Caller) => Effect.Effect<{
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
relation: string | null;
|
|
39
|
+
roles: {
|
|
40
|
+
roleKey: string;
|
|
41
|
+
roleName: string;
|
|
42
|
+
permissions: readonly string[];
|
|
43
|
+
}[];
|
|
44
|
+
}[], PartyDenied | PartyUnavailable, never>;
|
|
45
|
+
};
|