@zealamic/payload-auth-rbac-plugin 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/components/role-permission-matrix-client/default-data.js +2 -2
  2. package/dist/components/role-permission-matrix-client/default-data.js.map +1 -1
  3. package/dist/components/role-permission-matrix-client/index.js +2 -2
  4. package/dist/components/role-permission-matrix-client/index.js.map +1 -1
  5. package/dist/components/role-permission-matrix-client/matrix.module.scss +1 -4
  6. package/dist/components/role-permission-matrix-client/types.d.ts +4 -6
  7. package/dist/components/role-permission-matrix-client/types.js.map +1 -1
  8. package/docs/TRANSLATIONS.md +12 -5
  9. package/package.json +4 -27
  10. package/src/collections/permission-actions/default-data.ts +36 -0
  11. package/src/collections/permission-actions/index.ts +144 -0
  12. package/src/collections/permission-actions/types.ts +56 -0
  13. package/src/collections/permission-features/default-data.ts +30 -0
  14. package/src/collections/permission-features/index.ts +122 -0
  15. package/src/collections/permission-features/types.ts +47 -0
  16. package/src/collections/permissions/default-data.ts +38 -0
  17. package/src/collections/permissions/index.ts +160 -0
  18. package/src/collections/permissions/types.ts +57 -0
  19. package/src/collections/roles/default-data.ts +44 -0
  20. package/src/collections/roles/hooks/sync-permission-matrix-draft.ts +73 -0
  21. package/src/collections/roles/index.ts +178 -0
  22. package/src/collections/roles/types.ts +56 -0
  23. package/src/collections/roles-permissions/default-data.ts +28 -0
  24. package/src/collections/roles-permissions/index.ts +107 -0
  25. package/src/collections/roles-permissions/types.ts +42 -0
  26. package/src/collections/users/default-data.ts +19 -0
  27. package/src/collections/users/index.ts +148 -0
  28. package/src/collections/users/parent-path.ts +310 -0
  29. package/src/collections/users/types.ts +25 -0
  30. package/src/components/role-permission-matrix-client/default-data.ts +25 -0
  31. package/src/components/role-permission-matrix-client/index.tsx +369 -0
  32. package/src/components/role-permission-matrix-client/matrix.module.scss +66 -0
  33. package/src/components/role-permission-matrix-client/types.ts +16 -0
  34. package/src/endpoints/customEndpointHandler.ts +5 -0
  35. package/src/exports/client.ts +1 -0
  36. package/src/exports/rsc.ts +0 -0
  37. package/src/general-types.d.ts +5 -0
  38. package/src/index.ts +249 -0
  39. package/src/lib/constants/general.ts +1 -0
  40. package/src/lib/constants/index.ts +15 -0
  41. package/src/lib/constants/permission-action.ts +9 -0
  42. package/src/lib/constants/permission-feature.ts +4 -0
  43. package/src/lib/constants/permission.ts +4 -0
  44. package/src/lib/constants/role.ts +10 -0
  45. package/src/lib/constants/user.ts +1 -0
  46. package/src/lib/utils/access.ts +611 -0
  47. package/src/lib/utils/data.ts +7 -0
  48. package/src/lib/utils/fields.ts +62 -0
  49. package/src/lib/utils/index.ts +4 -0
  50. package/src/lib/utils/localization.ts +106 -0
  51. package/src/styles/variables.scss +1 -0
  52. package/src/types.ts +64 -0
@@ -0,0 +1,310 @@
1
+ import type {
2
+ CollectionAfterChangeHook,
3
+ CollectionAfterDeleteHook,
4
+ CollectionBeforeChangeHook,
5
+ PayloadRequest,
6
+ } from "payload";
7
+ import { PARENT_PATH_SEPARATOR } from "../../lib/constants/user.js";
8
+ import { toID } from "../../lib/utils/data.js";
9
+
10
+ type UserDoc = {
11
+ id?: string | number;
12
+ parent?: ItemRef;
13
+ parentPath?: string | null;
14
+ };
15
+
16
+ const getParentId = (parent: ItemRef | null | undefined): string | undefined => {
17
+ const id = toID(parent ?? undefined);
18
+ return id || undefined;
19
+ };
20
+
21
+ /** Ancestor IDs from root to parent (exclusive of self), e.g. `"1,2"`. */
22
+ export const buildParentPathFromParentDoc = (
23
+ parent: UserDoc | null | undefined,
24
+ ): string => {
25
+ if (!parent?.id) {
26
+ return "";
27
+ }
28
+
29
+ const parentId = String(parent.id);
30
+ const ancestorPath = parent.parentPath?.trim();
31
+
32
+ if (!ancestorPath) {
33
+ return parentId;
34
+ }
35
+
36
+ return `${ancestorPath}${PARENT_PATH_SEPARATOR}${parentId}`;
37
+ };
38
+
39
+ export const computeParentPath = async ({
40
+ parentId,
41
+ req,
42
+ userSlug,
43
+ }: {
44
+ parentId?: string;
45
+ req: PayloadRequest;
46
+ userSlug: string;
47
+ }): Promise<string> => {
48
+ if (!parentId) {
49
+ return "";
50
+ }
51
+
52
+ const parentDoc = await req.payload.findByID({
53
+ collection: userSlug,
54
+ id: parentId,
55
+ depth: 0,
56
+ req,
57
+ });
58
+
59
+ return buildParentPathFromParentDoc(parentDoc as UserDoc);
60
+ };
61
+
62
+ const pathContainsId = (parentPath: string, id: string): boolean => {
63
+ if (!parentPath) {
64
+ return false;
65
+ }
66
+
67
+ return parentPath.split(PARENT_PATH_SEPARATOR).includes(id);
68
+ };
69
+
70
+ const validateParentAssignment = async ({
71
+ userId,
72
+ parentId,
73
+ req,
74
+ userSlug,
75
+ }: {
76
+ userId?: string | number;
77
+ parentId?: string;
78
+ req: PayloadRequest;
79
+ userSlug: string;
80
+ }): Promise<string | true> => {
81
+ if (!parentId) {
82
+ return true;
83
+ }
84
+
85
+ if (userId && String(parentId) === String(userId)) {
86
+ return "A user cannot be their own parent.";
87
+ }
88
+
89
+ if (!userId) {
90
+ return true;
91
+ }
92
+
93
+ const parentDoc = await req.payload.findByID({
94
+ collection: userSlug,
95
+ id: parentId,
96
+ depth: 0,
97
+ req,
98
+ });
99
+
100
+ const parentPath = (parentDoc as UserDoc).parentPath ?? "";
101
+
102
+ if (pathContainsId(parentPath, String(userId))) {
103
+ return "Cannot assign a descendant as parent (would create a cycle).";
104
+ }
105
+
106
+ return true;
107
+ };
108
+
109
+ const syncDescendantParentPaths = async ({
110
+ userId,
111
+ req,
112
+ userSlug,
113
+ }: {
114
+ userId: string | number;
115
+ req: PayloadRequest;
116
+ userSlug: string;
117
+ }): Promise<void> => {
118
+ const children = await req.payload.find({
119
+ collection: userSlug,
120
+ depth: 0,
121
+ limit: 0,
122
+ pagination: false,
123
+ req,
124
+ where: {
125
+ parent: { equals: userId },
126
+ },
127
+ });
128
+
129
+ for (const child of children.docs as UserDoc[]) {
130
+ if (!child.id) {
131
+ continue;
132
+ }
133
+
134
+ const parentId = getParentId(child.parent);
135
+ const nextParentPath = await computeParentPath({
136
+ parentId,
137
+ req,
138
+ userSlug,
139
+ });
140
+
141
+ if ((child.parentPath ?? "") === nextParentPath) {
142
+ await syncDescendantParentPaths({
143
+ userId: child.id,
144
+ req,
145
+ userSlug,
146
+ });
147
+ continue;
148
+ }
149
+
150
+ await req.payload.update({
151
+ collection: userSlug,
152
+ id: child.id,
153
+ data: { parentPath: nextParentPath },
154
+ depth: 0,
155
+ req,
156
+ overrideAccess: true,
157
+ });
158
+
159
+ await syncDescendantParentPaths({
160
+ userId: child.id,
161
+ req,
162
+ userSlug,
163
+ });
164
+ }
165
+ };
166
+
167
+ export const createUserParentPathHooks = (userSlug: string) => {
168
+ const beforeChange: CollectionBeforeChangeHook = async ({
169
+ data,
170
+ req,
171
+ operation,
172
+ originalDoc,
173
+ }) => {
174
+ const incoming = data as UserDoc;
175
+ const previous = originalDoc as UserDoc | undefined;
176
+
177
+ const parentRef =
178
+ incoming.parent !== undefined
179
+ ? incoming.parent
180
+ : operation === "update"
181
+ ? previous?.parent
182
+ : undefined;
183
+
184
+ const parentId = getParentId(parentRef);
185
+
186
+ const validation = await validateParentAssignment({
187
+ userId:
188
+ operation === "update"
189
+ ? (previous?.id ?? incoming.id)
190
+ : incoming.id,
191
+ parentId,
192
+ req,
193
+ userSlug,
194
+ });
195
+
196
+ if (validation !== true) {
197
+ throw new Error(validation);
198
+ }
199
+
200
+ incoming.parentPath = await computeParentPath({
201
+ parentId,
202
+ req,
203
+ userSlug,
204
+ });
205
+
206
+ return incoming;
207
+ };
208
+
209
+ const afterChange: CollectionAfterChangeHook = async ({
210
+ doc,
211
+ previousDoc,
212
+ req,
213
+ operation,
214
+ }) => {
215
+ const current = doc as UserDoc;
216
+ const previous = previousDoc as UserDoc | undefined;
217
+
218
+ if (!current.id) {
219
+ return doc;
220
+ }
221
+
222
+ const previousParentId = getParentId(previous?.parent);
223
+ const nextParentId = getParentId(current.parent);
224
+
225
+ const parentChanged =
226
+ operation === "create" || previousParentId !== nextParentId;
227
+
228
+ if (parentChanged) {
229
+ await syncDescendantParentPaths({
230
+ userId: current.id,
231
+ req,
232
+ userSlug,
233
+ });
234
+ }
235
+
236
+ return doc;
237
+ };
238
+
239
+ const afterDelete: CollectionAfterDeleteHook = async ({ doc, req }) => {
240
+ const deleted = doc as UserDoc | undefined;
241
+
242
+ if (!deleted?.id) {
243
+ return;
244
+ }
245
+
246
+ const children = await req.payload.find({
247
+ collection: userSlug,
248
+ depth: 0,
249
+ limit: 0,
250
+ pagination: false,
251
+ req,
252
+ where: {
253
+ parent: { equals: deleted.id },
254
+ },
255
+ });
256
+
257
+ for (const child of children.docs as UserDoc[]) {
258
+ if (!child.id) {
259
+ continue;
260
+ }
261
+
262
+ await req.payload.update({
263
+ collection: userSlug,
264
+ id: child.id,
265
+ data: {
266
+ parent: null,
267
+ parentPath: "",
268
+ },
269
+ depth: 0,
270
+ req,
271
+ overrideAccess: true,
272
+ });
273
+
274
+ await syncDescendantParentPaths({
275
+ userId: child.id,
276
+ req,
277
+ userSlug,
278
+ });
279
+ }
280
+ };
281
+
282
+ return { beforeChange, afterChange, afterDelete };
283
+ };
284
+
285
+ const mergeHookArrays = <T>(existing: T[] | T | undefined, added: T): T[] => {
286
+ const base = Array.isArray(existing) ? existing : existing ? [existing] : [];
287
+ return [...base, added];
288
+ };
289
+
290
+ export const mergeUserCollectionHooks = ({
291
+ existingHooks,
292
+ userSlug,
293
+ }: {
294
+ existingHooks?: {
295
+ beforeChange?: CollectionBeforeChangeHook[] | CollectionBeforeChangeHook;
296
+ afterChange?: CollectionAfterChangeHook[] | CollectionAfterChangeHook;
297
+ afterDelete?: CollectionAfterDeleteHook[] | CollectionAfterDeleteHook;
298
+ };
299
+ userSlug: string;
300
+ }) => {
301
+ const { beforeChange, afterChange, afterDelete } =
302
+ createUserParentPathHooks(userSlug);
303
+
304
+ return {
305
+ ...existingHooks,
306
+ beforeChange: mergeHookArrays(existingHooks?.beforeChange, beforeChange),
307
+ afterChange: mergeHookArrays(existingHooks?.afterChange, afterChange),
308
+ afterDelete: mergeHookArrays(existingHooks?.afterDelete, afterDelete),
309
+ };
310
+ };
@@ -0,0 +1,25 @@
1
+ import type { Field } from "payload";
2
+
3
+ export type UsersModificationTranslations = {
4
+ [locale: string]: {
5
+ fields?: {
6
+ isSuperAdmin?: {
7
+ label?: string;
8
+ };
9
+ roles?: {
10
+ label?: string;
11
+ placeholder?: string;
12
+ };
13
+ parent?: {
14
+ label?: string;
15
+ placeholder?: string;
16
+ };
17
+ };
18
+ };
19
+ };
20
+
21
+ export type UsersModificationParams = {
22
+ translations?: UsersModificationTranslations;
23
+ fields?: Field[];
24
+ rolesSlug?: string;
25
+ };
@@ -0,0 +1,25 @@
1
+ import type { RolePermissionMatrixClientTranslations } from "./types.js";
2
+
3
+ export const rolePermissionMatrixClientDefaultTranslations: RolePermissionMatrixClientTranslations =
4
+ {
5
+ en: {
6
+ viewInUpdateScreenOnly: {
7
+ label: "View permission matrix in update screen only",
8
+ },
9
+ loading: {
10
+ placeholder: "Loading permission matrix...",
11
+ },
12
+ title: "Permission Matrix",
13
+ featuresLabel: "Features",
14
+ features: {
15
+ users: "Users",
16
+ },
17
+ actionsLabel: "Actions",
18
+ actions: {
19
+ create: "Create",
20
+ read: "Read",
21
+ update: "Update",
22
+ delete: "Delete",
23
+ },
24
+ },
25
+ };