@abinnovision/payloadcms-mcpx 2.0.0-beta.18 → 2.0.0-beta.20
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/README.md +2 -2
- package/dist/admin/capability-matrix.d.mts +2 -1
- package/dist/admin/capability-matrix.mjs +291 -340
- package/dist/admin/capability-toggles.mjs +24 -31
- package/dist/admin/confirmations-panel.mjs +61 -71
- package/dist/admin/confirmations.mjs +2 -1
- package/dist/admin/endpoint-url.mjs +7 -0
- package/dist/admin/setup-guide.mjs +2 -1
- package/dist/api-keys/capability-matrix.d.mts +4 -7
- package/dist/api-keys/capability-matrix.mjs +12 -51
- package/dist/api-keys/collection.mjs +10 -17
- package/dist/api-keys/confirmation-view.mjs +2 -1
- package/dist/api-keys/fields.mjs +53 -69
- package/dist/auth/resolve.mjs +29 -10
- package/dist/capabilities.d.mts +2 -0
- package/dist/capabilities.mjs +62 -19
- package/dist/endpoint/confirmations.mjs +14 -14
- package/dist/endpoint/handler.mjs +6 -22
- package/dist/endpoint/http.mjs +27 -0
- package/dist/endpoint/upload.mjs +11 -43
- package/dist/grants/confirmation.mjs +11 -42
- package/dist/grants/grant.mjs +58 -40
- package/dist/guards.mjs +8 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/options.mjs +34 -54
- package/dist/plugin.mjs +2 -1
- package/dist/request.mjs +5 -1
- package/dist/result.mjs +11 -1
- package/dist/schema/admin-hidden.mjs +4 -4
- package/dist/schema/describe.mjs +12 -18
- package/dist/schema/index.mjs +5 -6
- package/dist/schema/lexical-pointer.mjs +8 -8
- package/dist/schema/lexical.mjs +15 -13
- package/dist/schema/outline.mjs +4 -5
- package/dist/schema/path.mjs +4 -1
- package/dist/schema/pointer.mjs +11 -17
- package/dist/schema/shape.mjs +38 -50
- package/dist/schema/walk.mjs +57 -18
- package/dist/tools/create-document.mjs +16 -44
- package/dist/tools/delete-document.mjs +14 -17
- package/dist/tools/describe-schema.mjs +7 -5
- package/dist/tools/document.mjs +130 -44
- package/dist/tools/duplicate-document.mjs +11 -10
- package/dist/tools/find-documents.mjs +15 -20
- package/dist/tools/find-versions.mjs +10 -14
- package/dist/tools/get-document.mjs +8 -12
- package/dist/tools/links.mjs +2 -1
- package/dist/tools/list-capabilities.mjs +2 -1
- package/dist/tools/patch-document.mjs +12 -44
- package/dist/tools/publish-document.mjs +9 -27
- package/dist/tools/query-paths.mjs +1 -1
- package/dist/tools/run-confirmed.mjs +38 -50
- package/dist/tools/shared.mjs +43 -41
- package/dist/tools/validate-document.mjs +4 -6
- package/dist/tools/versions.mjs +11 -6
- package/dist/types.d.mts +12 -26
- package/dist/upload/file.mjs +10 -18
- package/dist/write/draft-guard.mjs +20 -20
- package/dist/write/patch.mjs +4 -4
- package/dist/write/publish-intent.mjs +22 -28
- package/dist/write/row-ids.mjs +3 -1
- package/dist/write/write-data.mjs +18 -31
- package/package.json +5 -5
- package/dist/builtin-tool-names.mjs +0 -19
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ACCESS_CHAIN, STORED_OPERATIONS, requiresOf } from "../capabilities.mjs";
|
|
2
|
+
import { cellPath, rowOperations, toolPath } from "../api-keys/capability-matrix.mjs";
|
|
2
3
|
//#region src/admin/capability-toggles.ts
|
|
3
4
|
/**
|
|
4
5
|
* How far a key may go with an entity. Each level includes the ones before it.
|
|
5
|
-
*/ const ACCESS_LEVELS = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
];
|
|
6
|
+
*/ const ACCESS_LEVELS = ["none", ...ACCESS_CHAIN.map(({ id }) => id)];
|
|
7
|
+
const toolGranted = (values, basePath, name) => values[toolPath(basePath, name)] === true;
|
|
8
|
+
const cellGranted = (values, basePath, namespace, row, operation) => values[cellPath(basePath, namespace, row.fieldName, operation)] === true;
|
|
9
|
+
/**
|
|
10
|
+
* One value per stored operation.
|
|
11
|
+
*/ const byOperation = (valueOf) => Object.fromEntries(STORED_OPERATIONS.map(({ id }) => [id, valueOf(id)]));
|
|
11
12
|
const toolsState = (matrix, basePath, values) => {
|
|
12
|
-
const granted = matrix.tools.filter((tool) => values
|
|
13
|
+
const granted = matrix.tools.filter((tool) => toolGranted(values, basePath, tool.name)).length;
|
|
13
14
|
if (granted === 0) return "off";
|
|
14
15
|
return granted === matrix.tools.length ? "on" : "mixed";
|
|
15
16
|
};
|
|
@@ -21,24 +22,19 @@ const toolsState = (matrix, basePath, values) => {
|
|
|
21
22
|
* config exposes. A write without a read reads as none, as on the server.
|
|
22
23
|
*/ const accessLevelOf = (basePath, namespace, row, values) => {
|
|
23
24
|
let reached = "none";
|
|
24
|
-
for (const
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
reached = level;
|
|
25
|
+
for (const { id } of ACCESS_CHAIN) {
|
|
26
|
+
if (!row[id] || !cellGranted(values, basePath, namespace, row, id)) break;
|
|
27
|
+
reached = id;
|
|
28
28
|
}
|
|
29
29
|
return reached;
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
32
|
* The delete mode a row's stored flags read as.
|
|
33
33
|
*/ const deleteModeOf = (basePath, namespace, row, values) => {
|
|
34
|
-
const granted = (operation) => values
|
|
34
|
+
const granted = (operation) => cellGranted(values, basePath, namespace, row, operation);
|
|
35
35
|
if (!granted("read") || !granted("delete")) return "off";
|
|
36
36
|
return row.deleteUnattended && granted("deleteUnattended") ? "trash" : "approve";
|
|
37
37
|
};
|
|
38
|
-
const requiresOf = (operation) => {
|
|
39
|
-
const found = STORED_OPERATIONS.find((candidate) => candidate.id === operation);
|
|
40
|
-
return found && "requires" in found ? found.requires : void 0;
|
|
41
|
-
};
|
|
42
38
|
const prerequisitesOf = (operation) => {
|
|
43
39
|
const required = requiresOf(operation);
|
|
44
40
|
return required ? [required, ...prerequisitesOf(required)] : [];
|
|
@@ -50,18 +46,18 @@ const reconcile = (next, touched) => {
|
|
|
50
46
|
return result;
|
|
51
47
|
};
|
|
52
48
|
const rowActions = (basePath, namespace, row, values, changes) => {
|
|
53
|
-
const current =
|
|
49
|
+
const current = byOperation((operation) => cellGranted(values, basePath, namespace, row, operation));
|
|
54
50
|
const touched = /* @__PURE__ */ new Set();
|
|
55
51
|
const next = { ...current };
|
|
56
|
-
for (const operation of
|
|
52
|
+
for (const operation of rowOperations(row)) {
|
|
57
53
|
const change = changes[operation.id];
|
|
58
|
-
if (change !== void 0
|
|
54
|
+
if (change !== void 0) {
|
|
59
55
|
next[operation.id] = change;
|
|
60
56
|
touched.add(operation.id);
|
|
61
57
|
}
|
|
62
58
|
}
|
|
63
59
|
const reconciled = reconcile(next, touched);
|
|
64
|
-
return
|
|
60
|
+
return rowOperations(row).filter((operation) => reconciled[operation.id] !== current[operation.id]).map((operation) => ({
|
|
65
61
|
type: "UPDATE",
|
|
66
62
|
path: cellPath(basePath, namespace, row.fieldName, operation.id),
|
|
67
63
|
value: reconciled[operation.id]
|
|
@@ -69,11 +65,7 @@ const rowActions = (basePath, namespace, row, values, changes) => {
|
|
|
69
65
|
};
|
|
70
66
|
const grantsOf = (level) => {
|
|
71
67
|
const rank = ACCESS_LEVELS.indexOf(level);
|
|
72
|
-
return {
|
|
73
|
-
read: rank >= 1,
|
|
74
|
-
write: rank >= 2,
|
|
75
|
-
publish: rank >= 3
|
|
76
|
-
};
|
|
68
|
+
return Object.fromEntries(ACCESS_CHAIN.map(({ id }, i) => [id, rank > i]));
|
|
77
69
|
};
|
|
78
70
|
/**
|
|
79
71
|
* The form-state updates one click implies, as literal actions. Every click
|
|
@@ -84,7 +76,7 @@ const grantsOf = (level) => {
|
|
|
84
76
|
* nothing does not mark the form modified. A level above a row's ceiling
|
|
85
77
|
* grants what the row exposes and no more.
|
|
86
78
|
*/ const buildToggleActions = (matrix, basePath, values, intent) => {
|
|
87
|
-
if (intent.kind === "tool" || intent.kind === "tools") return matrix.tools.filter((tool) => intent.kind === "tools" || tool.name === intent.name).filter((tool) => values
|
|
79
|
+
if (intent.kind === "tool" || intent.kind === "tools") return matrix.tools.filter((tool) => intent.kind === "tools" || tool.name === intent.name).filter((tool) => toolGranted(values, basePath, tool.name) !== intent.value).map((tool) => ({
|
|
88
80
|
type: "UPDATE",
|
|
89
81
|
path: toolPath(basePath, tool.name),
|
|
90
82
|
value: intent.value
|
|
@@ -96,22 +88,23 @@ const grantsOf = (level) => {
|
|
|
96
88
|
};
|
|
97
89
|
return rows.flatMap((row) => rowActions(basePath, intent.namespace, row, values, changes));
|
|
98
90
|
};
|
|
91
|
+
const firstNoop = (candidates, matrix, basePath, values, intentOf) => candidates.find((candidate) => buildToggleActions(matrix, basePath, values, intentOf(candidate)).length === 0);
|
|
99
92
|
/**
|
|
100
93
|
* The level a namespace's "All" control shows: the one that would change
|
|
101
94
|
* nothing if picked, so rows capped below it still count. `undefined` while
|
|
102
95
|
* the rows differ.
|
|
103
|
-
*/ const allAccessLevel = (matrix, basePath, namespace, values) => ACCESS_LEVELS
|
|
96
|
+
*/ const allAccessLevel = (matrix, basePath, namespace, values) => firstNoop(ACCESS_LEVELS, matrix, basePath, values, (level) => ({
|
|
104
97
|
kind: "access",
|
|
105
98
|
namespace,
|
|
106
99
|
level
|
|
107
|
-
})
|
|
100
|
+
}));
|
|
108
101
|
/**
|
|
109
102
|
* The delete mode the "All" control shows, by the same rule. It offers no
|
|
110
103
|
* trash, so a row that trashes directly reads as mixed.
|
|
111
|
-
*/ const allDeleteMode = (matrix, basePath, namespace, values) => ["off", "approve"]
|
|
104
|
+
*/ const allDeleteMode = (matrix, basePath, namespace, values) => firstNoop(["off", "approve"], matrix, basePath, values, (mode) => ({
|
|
112
105
|
kind: "deleteMode",
|
|
113
106
|
namespace,
|
|
114
107
|
mode
|
|
115
|
-
})
|
|
108
|
+
}));
|
|
116
109
|
//#endregion
|
|
117
110
|
export { accessLevelOf, accessLevelsOf, allAccessLevel, allDeleteMode, buildToggleActions, deleteModeOf, toolsState };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Button } from "@payloadcms/ui";
|
|
4
4
|
//#region src/admin/confirmations-panel.tsx
|
|
5
5
|
/**
|
|
@@ -58,11 +58,42 @@ const styles = {
|
|
|
58
58
|
gap: "calc(var(--base) * 0.5)"
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
|
+
const HEADS = [
|
|
62
|
+
"Collection",
|
|
63
|
+
"Document",
|
|
64
|
+
"Effect",
|
|
65
|
+
"Reason"
|
|
66
|
+
];
|
|
61
67
|
const groupsOf = (confirmations) => {
|
|
62
68
|
const groups = /* @__PURE__ */ new Map();
|
|
63
|
-
for (const confirmation of confirmations)
|
|
69
|
+
for (const confirmation of confirmations) {
|
|
70
|
+
const entries = groups.get(confirmation.group) ?? [];
|
|
71
|
+
entries.push(confirmation);
|
|
72
|
+
groups.set(confirmation.group, entries);
|
|
73
|
+
}
|
|
64
74
|
return [...groups];
|
|
65
75
|
};
|
|
76
|
+
const DecisionButtons = ({ approve, busy, onDecide, reject, small = false }) => /*#__PURE__*/ jsxs(Fragment, { children: [/*#__PURE__*/ jsx(Button, {
|
|
77
|
+
buttonStyle: "primary",
|
|
78
|
+
disabled: busy,
|
|
79
|
+
margin: false,
|
|
80
|
+
onClick: () => {
|
|
81
|
+
onDecide("approved");
|
|
82
|
+
},
|
|
83
|
+
size: small ? "small" : "medium",
|
|
84
|
+
type: "button",
|
|
85
|
+
children: approve
|
|
86
|
+
}), /*#__PURE__*/ jsx(Button, {
|
|
87
|
+
buttonStyle: "secondary",
|
|
88
|
+
disabled: busy,
|
|
89
|
+
margin: false,
|
|
90
|
+
onClick: () => {
|
|
91
|
+
onDecide("rejected");
|
|
92
|
+
},
|
|
93
|
+
size: small ? "small" : "medium",
|
|
94
|
+
type: "button",
|
|
95
|
+
children: reject
|
|
96
|
+
})] });
|
|
66
97
|
const approveAllLabel = (confirmations) => {
|
|
67
98
|
const permanent = confirmations.filter((confirmation) => confirmation.summary.permanent).length;
|
|
68
99
|
return permanent === 0 ? `Approve all (${String(confirmations.length)})` : `Approve all (${String(confirmations.length)}, ${String(permanent)} permanent)`;
|
|
@@ -124,29 +155,17 @@ const Row = ({ busy, confirmation, onDecide }) => {
|
|
|
124
155
|
}),
|
|
125
156
|
/*#__PURE__*/ jsx("td", {
|
|
126
157
|
style: cell,
|
|
127
|
-
children: /*#__PURE__*/
|
|
158
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
128
159
|
style: styles.actions,
|
|
129
|
-
children:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
onDecide([handle], "approved");
|
|
135
|
-
},
|
|
136
|
-
size: "small",
|
|
137
|
-
type: "button",
|
|
138
|
-
children: "Approve"
|
|
139
|
-
}), /*#__PURE__*/ jsx(Button, {
|
|
140
|
-
buttonStyle: "secondary",
|
|
141
|
-
disabled: busy,
|
|
142
|
-
margin: false,
|
|
143
|
-
onClick: () => {
|
|
144
|
-
onDecide([handle], "rejected");
|
|
160
|
+
children: /*#__PURE__*/ jsx(DecisionButtons, {
|
|
161
|
+
approve: "Approve",
|
|
162
|
+
busy,
|
|
163
|
+
onDecide: (decision) => {
|
|
164
|
+
onDecide([handle], decision);
|
|
145
165
|
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
})]
|
|
166
|
+
reject: "Reject",
|
|
167
|
+
small: true
|
|
168
|
+
})
|
|
150
169
|
})
|
|
151
170
|
})
|
|
152
171
|
]
|
|
@@ -177,63 +196,34 @@ const Row = ({ busy, confirmation, onDecide }) => {
|
|
|
177
196
|
children: group
|
|
178
197
|
}), /*#__PURE__*/ jsxs("table", {
|
|
179
198
|
style: styles.table,
|
|
180
|
-
children: [/*#__PURE__*/ jsx("thead", { children: /*#__PURE__*/ jsxs("tr", { children: [
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
children: "
|
|
190
|
-
}),
|
|
191
|
-
/*#__PURE__*/ jsx("th", {
|
|
192
|
-
scope: "col",
|
|
193
|
-
style: styles.head,
|
|
194
|
-
children: "Effect"
|
|
195
|
-
}),
|
|
196
|
-
/*#__PURE__*/ jsx("th", {
|
|
197
|
-
scope: "col",
|
|
198
|
-
style: styles.head,
|
|
199
|
-
children: "Reason"
|
|
200
|
-
}),
|
|
201
|
-
/*#__PURE__*/ jsx("th", {
|
|
202
|
-
scope: "col",
|
|
203
|
-
style: styles.head,
|
|
204
|
-
children: /*#__PURE__*/ jsx("span", {
|
|
205
|
-
className: "sr-only",
|
|
206
|
-
children: "Decision"
|
|
207
|
-
})
|
|
199
|
+
children: [/*#__PURE__*/ jsx("thead", { children: /*#__PURE__*/ jsxs("tr", { children: [HEADS.map((head) => /*#__PURE__*/ jsx("th", {
|
|
200
|
+
scope: "col",
|
|
201
|
+
style: styles.head,
|
|
202
|
+
children: head
|
|
203
|
+
}, head)), /*#__PURE__*/ jsx("th", {
|
|
204
|
+
scope: "col",
|
|
205
|
+
style: styles.head,
|
|
206
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
207
|
+
className: "sr-only",
|
|
208
|
+
children: "Decision"
|
|
208
209
|
})
|
|
209
|
-
] }) }), /*#__PURE__*/ jsx("tbody", { children: entries.map((confirmation) => /*#__PURE__*/ jsx(Row, {
|
|
210
|
+
})] }) }), /*#__PURE__*/ jsx("tbody", { children: entries.map((confirmation) => /*#__PURE__*/ jsx(Row, {
|
|
210
211
|
busy,
|
|
211
212
|
confirmation,
|
|
212
213
|
onDecide
|
|
213
214
|
}, confirmation.handle)) })]
|
|
214
215
|
})]
|
|
215
216
|
}, group)),
|
|
216
|
-
/*#__PURE__*/
|
|
217
|
+
/*#__PURE__*/ jsx("div", {
|
|
217
218
|
style: styles.footer,
|
|
218
|
-
children:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
onDecide(handles, "approved");
|
|
224
|
-
},
|
|
225
|
-
type: "button",
|
|
226
|
-
children: approveAllLabel(confirmations)
|
|
227
|
-
}), /*#__PURE__*/ jsx(Button, {
|
|
228
|
-
buttonStyle: "secondary",
|
|
229
|
-
disabled: busy,
|
|
230
|
-
margin: false,
|
|
231
|
-
onClick: () => {
|
|
232
|
-
onDecide(handles, "rejected");
|
|
219
|
+
children: /*#__PURE__*/ jsx(DecisionButtons, {
|
|
220
|
+
approve: approveAllLabel(confirmations),
|
|
221
|
+
busy,
|
|
222
|
+
onDecide: (decision) => {
|
|
223
|
+
onDecide(handles, decision);
|
|
233
224
|
},
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
})]
|
|
225
|
+
reject: "Reject all"
|
|
226
|
+
})
|
|
237
227
|
})
|
|
238
228
|
]
|
|
239
229
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { CONFIRMATIONS_PATH } from "../api-keys/confirmation-view.mjs";
|
|
3
3
|
import { ConfirmationsPanel, confirmationRowId } from "./confirmations-panel.mjs";
|
|
4
|
+
import { endpointUrl } from "./endpoint-url.mjs";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
5
6
|
import { toast, useConfig, useDocumentInfo } from "@payloadcms/ui";
|
|
6
7
|
import { useCallback, useEffect, useState } from "react";
|
|
@@ -15,7 +16,7 @@ import { useCallback, useEffect, useState } from "react";
|
|
|
15
16
|
const [confirmations, setConfirmations] = useState([]);
|
|
16
17
|
const [busy, setBusy] = useState(false);
|
|
17
18
|
const [version, setVersion] = useState(0);
|
|
18
|
-
const url = `${config.serverURL
|
|
19
|
+
const url = `${endpointUrl(config.serverURL, config.routes.api, endpointPath)}${CONFIRMATIONS_PATH}`;
|
|
19
20
|
useEffect(() => {
|
|
20
21
|
if (id === void 0) return;
|
|
21
22
|
let active = true;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/admin/endpoint-url.ts
|
|
2
|
+
/**
|
|
3
|
+
* The plugin endpoint's URL below the API route, without a doubled slash
|
|
4
|
+
* after the origin.
|
|
5
|
+
*/ const endpointUrl = (origin, apiRoute, endpointPath) => `${origin.replace(/\/+$/, "")}${apiRoute}${endpointPath}`;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { endpointUrl };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { endpointUrl } from "./endpoint-url.mjs";
|
|
2
3
|
import { buildSetupGuide } from "../api-keys/setup-guide.mjs";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
import { CopyToClipboard, useConfig, useDocumentInfo, useFormFields } from "@payloadcms/ui";
|
|
@@ -48,7 +49,7 @@ const styles = {
|
|
|
48
49
|
const origin = useOrigin(config.serverURL);
|
|
49
50
|
if (id === void 0) return null;
|
|
50
51
|
const sections = buildSetupGuide({
|
|
51
|
-
endpointUrl:
|
|
52
|
+
endpointUrl: endpointUrl(origin, config.routes.api, endpointPath),
|
|
52
53
|
apiKey: asString(apiKey),
|
|
53
54
|
label: asString(label)
|
|
54
55
|
});
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
+
import { McpxEntityCapabilities } from "../types.mjs";
|
|
1
2
|
import "../options.mjs";
|
|
3
|
+
import "../capabilities.mjs";
|
|
2
4
|
//#region src/api-keys/capability-matrix.d.ts
|
|
3
5
|
/**
|
|
4
6
|
* One entity's row. The booleans say what the plugin config exposes, not what
|
|
5
7
|
* the key was granted. A `false` leaves its segment out of the row's controls.
|
|
6
8
|
*/
|
|
7
|
-
export interface CapabilityRow {
|
|
9
|
+
export interface CapabilityRow extends McpxEntityCapabilities {
|
|
8
10
|
fieldName: string;
|
|
9
11
|
/**
|
|
10
12
|
* The slug, which is what MCP clients send and what refusals name.
|
|
11
13
|
*/
|
|
12
|
-
|
|
13
|
-
read: boolean;
|
|
14
|
-
write: boolean;
|
|
15
|
-
publish: boolean;
|
|
16
|
-
delete: boolean;
|
|
17
|
-
deleteUnattended: boolean;
|
|
14
|
+
slug: string;
|
|
18
15
|
/**
|
|
19
16
|
* Writes go live immediately, as the entity keeps no drafts.
|
|
20
17
|
*/
|
|
@@ -1,62 +1,23 @@
|
|
|
1
|
-
import { CAPABILITIES_FIELD,
|
|
1
|
+
import { CAPABILITIES_FIELD, STORED_OPERATIONS } from "../capabilities.mjs";
|
|
2
2
|
//#region src/api-keys/capability-matrix.ts
|
|
3
3
|
/**
|
|
4
4
|
* The group's `admin.description`, rendered by the matrix in Payload's
|
|
5
|
-
* description slot. It explains how to read the
|
|
6
|
-
*/ const CAPABILITIES_DESCRIPTION = "What this key may do. Each level includes the ones before it, and a missing segment means the plugin config does not expose it.";
|
|
7
|
-
/**
|
|
8
|
-
* The operations a collection or global can expose. The generated checkboxes
|
|
9
|
-
* and the matrix segments share this wording. `requires` names the
|
|
10
|
-
* operation a grant depends on, which the server and the matrix both enforce.
|
|
11
|
-
*/ const CAPABILITY_OPERATIONS = [
|
|
12
|
-
{
|
|
13
|
-
id: "read",
|
|
14
|
-
label: "Read",
|
|
15
|
-
description: "Describe, find and read."
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
id: "write",
|
|
19
|
-
label: "Write",
|
|
20
|
-
description: "Create, patch and validate drafts.",
|
|
21
|
-
requires: "read"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: "publish",
|
|
25
|
-
label: "Publish",
|
|
26
|
-
description: "Promote the current draft to what the public sees.",
|
|
27
|
-
requires: "write"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "delete",
|
|
31
|
-
label: "Delete",
|
|
32
|
-
description: "Delete documents. Each one is approved by this key's user in the admin panel, unless the config allows trashing directly and this key is set to.",
|
|
33
|
-
requires: "read"
|
|
34
|
-
}
|
|
35
|
-
];
|
|
36
|
-
/**
|
|
37
|
-
* Stored as a checkbox of its own but drawn as the Trash segment of the Delete
|
|
38
|
-
* control.
|
|
39
|
-
*/ const DELETE_UNATTENDED = {
|
|
40
|
-
id: "deleteUnattended",
|
|
41
|
-
label: "Trash directly",
|
|
42
|
-
description: "Move documents to trash without asking this key's user.",
|
|
43
|
-
requires: "delete"
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* Every operation with a stored checkbox: the capability operations plus
|
|
47
|
-
* the unattended delete.
|
|
48
|
-
*/ const STORED_OPERATIONS = [...CAPABILITY_OPERATIONS, DELETE_UNATTENDED];
|
|
5
|
+
* description slot. It explains how to read the controls.
|
|
6
|
+
*/ const CAPABILITIES_DESCRIPTION = "What this key may do. Each level includes the ones before it, and a missing segment means the plugin config does not expose it. A shield means this key's user approves each call.";
|
|
49
7
|
const toRow = (entity) => ({
|
|
50
8
|
fieldName: entity.fieldName,
|
|
51
|
-
|
|
9
|
+
slug: entity.slug,
|
|
52
10
|
read: entity.read,
|
|
53
|
-
write:
|
|
54
|
-
publish:
|
|
11
|
+
write: entity.write,
|
|
12
|
+
publish: entity.publish,
|
|
55
13
|
delete: entity.delete,
|
|
56
14
|
deleteUnattended: entity.deleteUnattended,
|
|
57
|
-
...
|
|
15
|
+
...entity.liveWrite ? { live: true } : {}
|
|
58
16
|
});
|
|
59
17
|
/**
|
|
18
|
+
* The operations a row exposes, in stored order.
|
|
19
|
+
*/ const rowOperations = (row) => STORED_OPERATIONS.filter((operation) => row[operation.id]);
|
|
20
|
+
/**
|
|
60
21
|
* The single description of what the config exposes. `createCapabilityFields`
|
|
61
22
|
* and the admin component both draw from it, so a control cannot appear without a
|
|
62
23
|
* field behind it.
|
|
@@ -72,6 +33,6 @@ const cellPath = (basePath, namespace, fieldName, operation) => `${basePath}.${n
|
|
|
72
33
|
const toolPath = (basePath, name) => `${basePath}.tools.${name}`;
|
|
73
34
|
/**
|
|
74
35
|
* Every path the matrix can address, in a stable order.
|
|
75
|
-
*/ const capabilityPaths = (matrix, basePath = CAPABILITIES_FIELD) => [...["collections", "globals"].flatMap((namespace) => matrix[namespace].flatMap((row) =>
|
|
36
|
+
*/ const capabilityPaths = (matrix, basePath = CAPABILITIES_FIELD) => [...["collections", "globals"].flatMap((namespace) => matrix[namespace].flatMap((row) => rowOperations(row).map((operation) => cellPath(basePath, namespace, row.fieldName, operation.id)))), ...matrix.tools.map((tool) => toolPath(basePath, tool.name))];
|
|
76
37
|
//#endregion
|
|
77
|
-
export { CAPABILITIES_DESCRIPTION,
|
|
38
|
+
export { CAPABILITIES_DESCRIPTION, capabilityPaths, cellPath, createCapabilityMatrix, rowOperations, toolPath };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createCapabilityFields, createConfirmationFields, createKeyFields,
|
|
1
|
+
import { SERVER_ONLY, createCapabilityFields, createConfirmationFields, createKeyFields, withKeyTabs } from "./fields.mjs";
|
|
2
2
|
import { generateApiKey, hashApiKey } from "./key.mjs";
|
|
3
3
|
//#region src/api-keys/collection.ts
|
|
4
4
|
/**
|
|
@@ -37,22 +37,15 @@ import { generateApiKey, hashApiKey } from "./key.mjs";
|
|
|
37
37
|
delete: ownKeysOnly
|
|
38
38
|
},
|
|
39
39
|
hooks: { beforeChange: [keyBeforeChange] },
|
|
40
|
-
fields: [...createConfirmationFields(options), ...
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
defaultValue: ({ req }) => isUser({ req }) ? req.user?.id : void 0,
|
|
51
|
-
admin: { description: "The user this key acts as." }
|
|
52
|
-
},
|
|
53
|
-
...createKeyFields(),
|
|
54
|
-
...createCapabilityFields(options)
|
|
55
|
-
], options)]
|
|
40
|
+
fields: [...createConfirmationFields(options), ...withKeyTabs([{
|
|
41
|
+
name: "user",
|
|
42
|
+
type: "relationship",
|
|
43
|
+
relationTo: userCollection,
|
|
44
|
+
required: true,
|
|
45
|
+
access: SERVER_ONLY,
|
|
46
|
+
defaultValue: ({ req }) => isUser({ req }) ? req.user?.id : void 0,
|
|
47
|
+
admin: { description: "The user this key acts as." }
|
|
48
|
+
}, ...createKeyFields()], createCapabilityFields(options), options)]
|
|
56
49
|
};
|
|
57
50
|
};
|
|
58
51
|
//#endregion
|