payload-nested-docs-page-tree 1.2.4 → 1.4.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.
- package/README.md +356 -222
- package/dist/components/PageTreeContext.d.ts +13 -1
- package/dist/components/PageTreeContext.js +11 -1
- package/dist/components/PageTreeContext.js.map +1 -1
- package/dist/components/PageTreeListView.client.d.ts +4 -5
- package/dist/components/PageTreeListView.client.js +668 -128
- package/dist/components/PageTreeListView.client.js.map +1 -1
- package/dist/components/PageTreeListView.module.css +399 -376
- package/dist/components/PageTreeListView.server.js +18 -28
- package/dist/components/PageTreeListView.server.js.map +1 -1
- package/dist/components/PageTreeTitleCell.js +80 -18
- package/dist/components/PageTreeTitleCell.js.map +1 -1
- package/dist/endpoints/createMovePageEndpoint.d.ts +3 -0
- package/dist/endpoints/createMovePageEndpoint.js +186 -125
- package/dist/endpoints/createMovePageEndpoint.js.map +1 -1
- package/dist/endpoints/createReorderPageEndpoint.d.ts +8 -0
- package/dist/endpoints/createReorderPageEndpoint.js +706 -0
- package/dist/endpoints/createReorderPageEndpoint.js.map +1 -0
- package/dist/endpoints/shared.d.ts +40 -0
- package/dist/endpoints/shared.js +93 -0
- package/dist/endpoints/shared.js.map +1 -0
- package/dist/index.js +110 -27
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +47 -2
- package/dist/types.js +22 -3
- package/dist/types.js.map +1 -1
- package/dist/utilities/diagnostics.d.ts +42 -0
- package/dist/utilities/diagnostics.js +83 -0
- package/dist/utilities/diagnostics.js.map +1 -0
- package/dist/utilities/dropTargets.d.ts +2 -2
- package/dist/utilities/dropTargets.js +9 -7
- package/dist/utilities/dropTargets.js.map +1 -1
- package/dist/utilities/moveValidation.js.map +1 -1
- package/dist/utilities/pageTree.js +4 -6
- package/dist/utilities/pageTree.js.map +1 -1
- package/dist/utilities/pageTreeCollision.d.ts +6 -0
- package/dist/utilities/pageTreeCollision.js +39 -4
- package/dist/utilities/pageTreeCollision.js.map +1 -1
- package/dist/utilities/pageTreeConfig.js.map +1 -1
- package/dist/utilities/status.d.ts +1 -1
- package/dist/utilities/status.js +7 -3
- package/dist/utilities/status.js.map +1 -1
- package/package.json +54 -32
|
@@ -1,23 +1,14 @@
|
|
|
1
|
+
import { DIAGNOSTICS_FLOW_CONTEXT_KEY, createFlowID, readMainRowSnapshot } from '../utilities/diagnostics.js';
|
|
1
2
|
import { CANCEL_DRAG_MESSAGE } from '../utilities/moveValidation.js';
|
|
2
3
|
import { buildChildrenByParentID, collectDescendantIDs, getDocParentID, stringifyDocID } from '../utilities/pageTree.js';
|
|
3
|
-
import { pageTreeMoveContextKey } from '../types.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
4
|
+
import { pageTreeMoveContextKey, pageTreeWriteContextKey } from '../types.js';
|
|
5
|
+
import { assertUpdateAccess, collectionHasAutosaveDrafts, collectionHasDrafts, getRequestedLocale, normalizeID, respond, toCollectionID } from './shared.js';
|
|
6
|
+
const SNAPSHOT_FIELDS = [
|
|
7
|
+
'_status'
|
|
8
|
+
];
|
|
9
9
|
function hasOwnProperty(value, key) {
|
|
10
10
|
return Object.prototype.hasOwnProperty.call(value, key);
|
|
11
11
|
}
|
|
12
|
-
function normalizeID(value) {
|
|
13
|
-
if (value === null || value === undefined || value === '') {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
if (typeof value === 'number' || typeof value === 'string') {
|
|
17
|
-
return stringifyDocID(value);
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
12
|
function normalizeMoveDocumentBody(value) {
|
|
22
13
|
const incomingData = typeof value === 'string' ? (()=>{
|
|
23
14
|
try {
|
|
@@ -41,15 +32,33 @@ function normalizeMoveDocumentBody(value) {
|
|
|
41
32
|
};
|
|
42
33
|
}
|
|
43
34
|
async function readBodyFromRequest(req) {
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
35
|
+
let lastAttempt = null;
|
|
36
|
+
if (req.data !== undefined && req.data !== null) {
|
|
37
|
+
lastAttempt = {
|
|
38
|
+
raw: req.data,
|
|
39
|
+
source: 'req.data'
|
|
40
|
+
};
|
|
41
|
+
const directBody = normalizeMoveDocumentBody(req.data);
|
|
42
|
+
if (directBody) {
|
|
43
|
+
return {
|
|
44
|
+
body: directBody,
|
|
45
|
+
lastAttempt
|
|
46
|
+
};
|
|
47
|
+
}
|
|
47
48
|
}
|
|
48
49
|
if (typeof req.json === 'function') {
|
|
49
50
|
try {
|
|
50
|
-
const
|
|
51
|
+
const raw = await req.json();
|
|
52
|
+
lastAttempt = {
|
|
53
|
+
raw,
|
|
54
|
+
source: 'req.json'
|
|
55
|
+
};
|
|
56
|
+
const jsonBody = normalizeMoveDocumentBody(raw);
|
|
51
57
|
if (jsonBody) {
|
|
52
|
-
return
|
|
58
|
+
return {
|
|
59
|
+
body: jsonBody,
|
|
60
|
+
lastAttempt
|
|
61
|
+
};
|
|
53
62
|
}
|
|
54
63
|
} catch {
|
|
55
64
|
// Fall through to the text-based fallback when the runtime does not hydrate req.data.
|
|
@@ -57,98 +66,36 @@ async function readBodyFromRequest(req) {
|
|
|
57
66
|
}
|
|
58
67
|
if (typeof req.text === 'function') {
|
|
59
68
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const collection = getPayloadCollection({
|
|
72
|
-
collectionSlug,
|
|
73
|
-
req
|
|
74
|
-
});
|
|
75
|
-
if (!collection) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
const idType = collection.customIDType ?? req.payload.db.defaultIDType;
|
|
79
|
-
return idType === 'number';
|
|
80
|
-
}
|
|
81
|
-
function toCollectionID(args) {
|
|
82
|
-
const { collectionSlug, id, req } = args;
|
|
83
|
-
if (!usesNumericID({
|
|
84
|
-
collectionSlug,
|
|
85
|
-
req
|
|
86
|
-
})) {
|
|
87
|
-
return id;
|
|
88
|
-
}
|
|
89
|
-
return Number(id);
|
|
90
|
-
}
|
|
91
|
-
function getRequestedLocale(req) {
|
|
92
|
-
return req.locale && req.locale !== 'all' ? req.locale : undefined;
|
|
93
|
-
}
|
|
94
|
-
function collectionHasDrafts(args) {
|
|
95
|
-
return Boolean(getPayloadCollection(args)?.config?.versions?.drafts);
|
|
96
|
-
}
|
|
97
|
-
async function assertUpdateAccess(args) {
|
|
98
|
-
const { collectionSlug, movedID, nextParentID, parentFieldSlug, req } = args;
|
|
99
|
-
const collection = getPayloadCollection({
|
|
100
|
-
collectionSlug,
|
|
101
|
-
req
|
|
102
|
-
});
|
|
103
|
-
const updateAccess = collection?.config?.access?.update;
|
|
104
|
-
if (!updateAccess) {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
const accessResult = await updateAccess({
|
|
108
|
-
data: {
|
|
109
|
-
[parentFieldSlug]: nextParentID
|
|
110
|
-
},
|
|
111
|
-
id: movedID,
|
|
112
|
-
req
|
|
113
|
-
});
|
|
114
|
-
if (accessResult === false) {
|
|
115
|
-
return respond(403, {
|
|
116
|
-
message: req.i18n.t('error:unauthorized')
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
if (accessResult && typeof accessResult === 'object') {
|
|
120
|
-
const matchingDocs = await req.payload.find({
|
|
121
|
-
collection: collectionSlug,
|
|
122
|
-
depth: 0,
|
|
123
|
-
draft: collectionHasDrafts({
|
|
124
|
-
collectionSlug,
|
|
125
|
-
req
|
|
126
|
-
}) ? true : undefined,
|
|
127
|
-
limit: 1,
|
|
128
|
-
locale: getRequestedLocale(req),
|
|
129
|
-
overrideAccess: true,
|
|
130
|
-
req,
|
|
131
|
-
where: {
|
|
132
|
-
and: [
|
|
133
|
-
{
|
|
134
|
-
id: {
|
|
135
|
-
equals: movedID
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
accessResult
|
|
139
|
-
]
|
|
69
|
+
const raw = await req.text();
|
|
70
|
+
lastAttempt = {
|
|
71
|
+
raw,
|
|
72
|
+
source: 'req.text'
|
|
73
|
+
};
|
|
74
|
+
const textBody = normalizeMoveDocumentBody(raw);
|
|
75
|
+
if (textBody) {
|
|
76
|
+
return {
|
|
77
|
+
body: textBody,
|
|
78
|
+
lastAttempt
|
|
79
|
+
};
|
|
140
80
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
81
|
+
} catch {
|
|
82
|
+
return {
|
|
83
|
+
body: null,
|
|
84
|
+
lastAttempt
|
|
85
|
+
};
|
|
146
86
|
}
|
|
147
87
|
}
|
|
148
|
-
return
|
|
88
|
+
return {
|
|
89
|
+
body: null,
|
|
90
|
+
lastAttempt
|
|
91
|
+
};
|
|
149
92
|
}
|
|
150
93
|
export function createMovePageEndpoint(args) {
|
|
151
|
-
const { collectionSlug, parentFieldSlug } = args;
|
|
94
|
+
const { collectionSlug, diagnostics, parentFieldSlug, publishOnMove = false } = args;
|
|
95
|
+
const snapshotFields = [
|
|
96
|
+
...SNAPSHOT_FIELDS,
|
|
97
|
+
parentFieldSlug
|
|
98
|
+
];
|
|
152
99
|
return {
|
|
153
100
|
handler: async (req)=>{
|
|
154
101
|
const movedIDFromRoute = normalizeID(req.routeParams?.id);
|
|
@@ -157,8 +104,22 @@ export function createMovePageEndpoint(args) {
|
|
|
157
104
|
message: 'Document ID was not specified.'
|
|
158
105
|
});
|
|
159
106
|
}
|
|
160
|
-
const body = await readBodyFromRequest(req);
|
|
107
|
+
const { body, lastAttempt } = await readBodyFromRequest(req);
|
|
161
108
|
if (!body) {
|
|
109
|
+
if (diagnostics.enabled) {
|
|
110
|
+
diagnostics.log({
|
|
111
|
+
collection: collectionSlug,
|
|
112
|
+
data: {
|
|
113
|
+
movedID: movedIDFromRoute,
|
|
114
|
+
rawBody: lastAttempt?.raw ?? null,
|
|
115
|
+
rawBodySource: lastAttempt?.source ?? null
|
|
116
|
+
},
|
|
117
|
+
flow: createFlowID('move-endpoint'),
|
|
118
|
+
level: 'warn',
|
|
119
|
+
message: 'move endpoint rejected request body: parentID was missing or malformed',
|
|
120
|
+
source: 'move-endpoint:body-rejected'
|
|
121
|
+
});
|
|
122
|
+
}
|
|
162
123
|
return respond(400, {
|
|
163
124
|
message: 'A valid parentID is required.'
|
|
164
125
|
});
|
|
@@ -175,9 +136,10 @@ export function createMovePageEndpoint(args) {
|
|
|
175
136
|
});
|
|
176
137
|
const accessError = await assertUpdateAccess({
|
|
177
138
|
collectionSlug,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
139
|
+
data: {
|
|
140
|
+
[parentFieldSlug]: nextParentID
|
|
141
|
+
},
|
|
142
|
+
id: movedID,
|
|
181
143
|
req
|
|
182
144
|
});
|
|
183
145
|
if (accessError) {
|
|
@@ -233,28 +195,127 @@ export function createMovePageEndpoint(args) {
|
|
|
233
195
|
message: 'A document cannot be moved under one of its descendants.'
|
|
234
196
|
});
|
|
235
197
|
}
|
|
236
|
-
|
|
198
|
+
const flow = createFlowID('move-endpoint');
|
|
199
|
+
const moveStart = Date.now();
|
|
200
|
+
const hasDrafts = collectionHasDrafts({
|
|
201
|
+
collectionSlug,
|
|
202
|
+
req
|
|
203
|
+
});
|
|
204
|
+
const hasAutosave = hasDrafts && collectionHasAutosaveDrafts({
|
|
205
|
+
collectionSlug,
|
|
206
|
+
req
|
|
207
|
+
});
|
|
208
|
+
// With `publishOnMove`, publish the reparent right away instead of staging
|
|
209
|
+
// it — but ONLY when the moved doc had nothing staged before the move (its
|
|
210
|
+
// latest version, read above with `draft: true`, is already published). A
|
|
211
|
+
// doc with pending draft edits stays staged, so those in-progress edits are
|
|
212
|
+
// never published as a side effect of the move.
|
|
213
|
+
const publishMove = publishOnMove && hasDrafts && movedDoc._status === 'published';
|
|
214
|
+
const updateData = {
|
|
215
|
+
[parentFieldSlug]: body.parentID === null ? null : toCollectionID({
|
|
216
|
+
collectionSlug,
|
|
217
|
+
id: body.parentID,
|
|
218
|
+
req
|
|
219
|
+
}),
|
|
220
|
+
...publishMove ? {
|
|
221
|
+
_status: 'published'
|
|
222
|
+
} : {}
|
|
223
|
+
};
|
|
224
|
+
const updateArgs = {
|
|
237
225
|
collection: collectionSlug,
|
|
238
226
|
context: {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
227
|
+
// A published move changes the live URL of this page and of every
|
|
228
|
+
// descendant nested-docs resaves on this request, so the deploy
|
|
229
|
+
// opt-out flag is withheld and consumer rebuild hooks fire normally.
|
|
230
|
+
// A staged move changes nothing live and keeps the flag.
|
|
231
|
+
...publishMove ? {} : {
|
|
232
|
+
[pageTreeMoveContextKey]: true
|
|
233
|
+
},
|
|
234
|
+
[pageTreeWriteContextKey]: true
|
|
247
235
|
},
|
|
236
|
+
autosave: hasAutosave && !publishMove ? true : undefined,
|
|
237
|
+
data: updateData,
|
|
248
238
|
depth: 0,
|
|
249
|
-
draft:
|
|
250
|
-
collectionSlug,
|
|
251
|
-
req
|
|
252
|
-
}) ? true : undefined,
|
|
239
|
+
draft: hasDrafts && !publishMove ? true : undefined,
|
|
253
240
|
id: movedID,
|
|
254
241
|
locale: getRequestedLocale(req),
|
|
255
242
|
overrideAccess: true,
|
|
256
243
|
req
|
|
257
|
-
}
|
|
244
|
+
};
|
|
245
|
+
if (diagnostics.enabled) {
|
|
246
|
+
if (!req.context) {
|
|
247
|
+
req.context = {};
|
|
248
|
+
}
|
|
249
|
+
;
|
|
250
|
+
req.context[DIAGNOSTICS_FLOW_CONTEXT_KEY] = flow;
|
|
251
|
+
const beforeSnapshot = await readMainRowSnapshot({
|
|
252
|
+
collectionSlug,
|
|
253
|
+
fields: snapshotFields,
|
|
254
|
+
id: movedID,
|
|
255
|
+
req
|
|
256
|
+
});
|
|
257
|
+
diagnostics.log({
|
|
258
|
+
collection: collectionSlug,
|
|
259
|
+
data: {
|
|
260
|
+
autosave: updateArgs.autosave ?? null,
|
|
261
|
+
currentParentID,
|
|
262
|
+
draft: updateArgs.draft,
|
|
263
|
+
hasTransaction: Boolean(req.transactionID),
|
|
264
|
+
locale: updateArgs.locale ?? null,
|
|
265
|
+
movedDocStatus: movedDoc._status ?? null,
|
|
266
|
+
movedID: String(movedID),
|
|
267
|
+
nextParentID: body.parentID,
|
|
268
|
+
publishedMainRowBefore: beforeSnapshot,
|
|
269
|
+
publishMove,
|
|
270
|
+
updateData
|
|
271
|
+
},
|
|
272
|
+
flow,
|
|
273
|
+
level: 'info',
|
|
274
|
+
message: 'move endpoint entering: parent move only',
|
|
275
|
+
source: 'move-endpoint:enter'
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
const result = await req.payload.update(updateArgs);
|
|
280
|
+
if (diagnostics.enabled) {
|
|
281
|
+
const afterSnapshot = await readMainRowSnapshot({
|
|
282
|
+
collectionSlug,
|
|
283
|
+
fields: snapshotFields,
|
|
284
|
+
id: movedID,
|
|
285
|
+
req
|
|
286
|
+
});
|
|
287
|
+
diagnostics.log({
|
|
288
|
+
collection: collectionSlug,
|
|
289
|
+
data: {
|
|
290
|
+
durMs: Date.now() - moveStart,
|
|
291
|
+
movedID: String(movedID),
|
|
292
|
+
publishedMainRowAfter: afterSnapshot,
|
|
293
|
+
resultStatus: result?._status ?? null
|
|
294
|
+
},
|
|
295
|
+
flow,
|
|
296
|
+
level: 'info',
|
|
297
|
+
message: 'move endpoint payload.update succeeded',
|
|
298
|
+
source: 'move-endpoint:ok'
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
} catch (err) {
|
|
302
|
+
const error = err;
|
|
303
|
+
diagnostics.log({
|
|
304
|
+
collection: collectionSlug,
|
|
305
|
+
data: {
|
|
306
|
+
data: error?.data ?? null,
|
|
307
|
+
durMs: Date.now() - moveStart,
|
|
308
|
+
message: error?.message,
|
|
309
|
+
movedID: String(movedID),
|
|
310
|
+
name: error?.name
|
|
311
|
+
},
|
|
312
|
+
flow,
|
|
313
|
+
level: 'error',
|
|
314
|
+
message: `move endpoint payload.update failed: ${error?.message ?? 'unknown error'}`,
|
|
315
|
+
source: 'move-endpoint:error'
|
|
316
|
+
});
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
258
319
|
return respond(200, {
|
|
259
320
|
movedID: movedIDFromRoute,
|
|
260
321
|
ok: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/createMovePageEndpoint.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest, Where } from 'payload'\r\n\r\nimport { CANCEL_DRAG_MESSAGE } from '../utilities/moveValidation.js'\r\nimport {\r\n buildChildrenByParentID,\r\n collectDescendantIDs,\r\n getDocParentID,\r\n stringifyDocID,\r\n} from '../utilities/pageTree.js'\r\nimport { pageTreeMoveContextKey, type PageTreeSourceDoc } from '../types.js'\r\n\r\ntype MoveDocumentRequestBody = {\r\n parentID: null | string\r\n}\r\n\r\ntype PayloadCollectionLike = {\r\n config?: {\r\n access?: {\r\n update?: ((args: Record<string, unknown>) => Promise<Record<string, unknown> | boolean> | Record<string, unknown> | boolean) | undefined\r\n }\r\n versions?: {\r\n drafts?: unknown\r\n }\r\n }\r\n customIDType?: string\r\n}\r\n\r\nfunction respond(status: number, body: Record<string, unknown>) {\r\n return Response.json(body, { status })\r\n}\r\n\r\nfunction hasOwnProperty(value: object, key: PropertyKey): boolean {\r\n return Object.prototype.hasOwnProperty.call(value, key)\r\n}\r\n\r\nfunction normalizeID(value: unknown): null | string {\r\n if (value === null || value === undefined || value === '') {\r\n return null\r\n }\r\n\r\n if (typeof value === 'number' || typeof value === 'string') {\r\n return stringifyDocID(value)\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction normalizeMoveDocumentBody(value: unknown): MoveDocumentRequestBody | null {\r\n const incomingData =\r\n typeof value === 'string'\r\n ? (() => {\r\n try {\r\n return JSON.parse(value) as unknown\r\n } catch {\r\n return null\r\n }\r\n })()\r\n : value\r\n\r\n if (!incomingData || typeof incomingData !== 'object' || Array.isArray(incomingData)) {\r\n return null\r\n }\r\n\r\n if (!hasOwnProperty(incomingData, 'parentID')) {\r\n return null\r\n }\r\n\r\n const parentID = normalizeID((incomingData as { parentID?: unknown }).parentID)\r\n\r\n if (\r\n (incomingData as { parentID?: unknown }).parentID !== null &&\r\n (incomingData as { parentID?: unknown }).parentID !== undefined &&\r\n parentID === null\r\n ) {\r\n return null\r\n }\r\n\r\n return {\r\n parentID,\r\n }\r\n}\r\n\r\nasync function readBodyFromRequest(req: PayloadRequest): Promise<MoveDocumentRequestBody | null> {\r\n const directBody = normalizeMoveDocumentBody(req.data)\r\n\r\n if (directBody) {\r\n return directBody\r\n }\r\n\r\n if (typeof req.json === 'function') {\r\n try {\r\n const jsonBody = normalizeMoveDocumentBody(await req.json())\r\n\r\n if (jsonBody) {\r\n return jsonBody\r\n }\r\n } catch {\r\n // Fall through to the text-based fallback when the runtime does not hydrate req.data.\r\n }\r\n }\r\n\r\n if (typeof req.text === 'function') {\r\n try {\r\n return normalizeMoveDocumentBody(await req.text())\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction getPayloadCollection({\r\n collectionSlug,\r\n req,\r\n}: {\r\n collectionSlug: string\r\n req: PayloadRequest\r\n}): PayloadCollectionLike | undefined {\r\n return (req.payload.collections as unknown as Record<string, PayloadCollectionLike | undefined>)[\r\n collectionSlug\r\n ]\r\n}\r\n\r\nfunction usesNumericID({\r\n collectionSlug,\r\n req,\r\n}: {\r\n collectionSlug: string\r\n req: PayloadRequest\r\n}): boolean {\r\n const collection = getPayloadCollection({ collectionSlug, req })\r\n\r\n if (!collection) {\r\n return false\r\n }\r\n\r\n const idType = collection.customIDType ?? req.payload.db.defaultIDType\r\n\r\n return idType === 'number'\r\n}\r\n\r\nfunction toCollectionID(args: {\r\n collectionSlug: string\r\n id: string\r\n req: PayloadRequest\r\n}): number | string {\r\n const { collectionSlug, id, req } = args\r\n\r\n if (!usesNumericID({ collectionSlug, req })) {\r\n return id\r\n }\r\n\r\n return Number(id)\r\n}\r\n\r\nfunction getRequestedLocale(req: PayloadRequest): string | undefined {\r\n return req.locale && req.locale !== 'all' ? req.locale : undefined\r\n}\r\n\r\nfunction collectionHasDrafts(args: {\r\n collectionSlug: string\r\n req: PayloadRequest\r\n}): boolean {\r\n return Boolean(getPayloadCollection(args)?.config?.versions?.drafts)\r\n}\r\n\r\nasync function assertUpdateAccess(args: {\r\n collectionSlug: string\r\n movedID: number | string\r\n nextParentID: null | number | string\r\n parentFieldSlug: string\r\n req: PayloadRequest\r\n}): Promise<Response | null> {\r\n const { collectionSlug, movedID, nextParentID, parentFieldSlug, req } = args\r\n const collection = getPayloadCollection({ collectionSlug, req })\r\n const updateAccess = collection?.config?.access?.update\r\n\r\n if (!updateAccess) {\r\n return null\r\n }\r\n\r\n const accessResult = await updateAccess({\r\n data: { [parentFieldSlug]: nextParentID } as never,\r\n id: movedID as never,\r\n req,\r\n })\r\n\r\n if (accessResult === false) {\r\n return respond(403, {\r\n message: req.i18n.t('error:unauthorized'),\r\n })\r\n }\r\n\r\n if (accessResult && typeof accessResult === 'object') {\r\n const matchingDocs = await req.payload.find({\r\n collection: collectionSlug as never,\r\n depth: 0,\r\n draft: collectionHasDrafts({ collectionSlug, req }) ? true : undefined,\r\n limit: 1,\r\n locale: getRequestedLocale(req) as never,\r\n overrideAccess: true,\r\n req,\r\n where: {\r\n and: [\r\n {\r\n id: {\r\n equals: movedID,\r\n },\r\n },\r\n accessResult as Where,\r\n ],\r\n },\r\n } as never)\r\n\r\n if (matchingDocs.docs.length === 0) {\r\n return respond(403, {\r\n message: req.i18n.t('error:unauthorized'),\r\n })\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\nexport function createMovePageEndpoint(args: {\r\n collectionSlug: string\r\n parentFieldSlug: string\r\n}): Endpoint {\r\n const { collectionSlug, parentFieldSlug } = args\r\n\r\n return {\r\n handler: async (req) => {\r\n const movedIDFromRoute = normalizeID(req.routeParams?.id)\r\n\r\n if (!movedIDFromRoute) {\r\n return respond(400, {\r\n message: 'Document ID was not specified.',\r\n })\r\n }\r\n\r\n const body = await readBodyFromRequest(req)\r\n\r\n if (!body) {\r\n return respond(400, {\r\n message: 'A valid parentID is required.',\r\n })\r\n }\r\n\r\n const movedID = toCollectionID({\r\n collectionSlug,\r\n id: movedIDFromRoute,\r\n req,\r\n })\r\n const nextParentID =\r\n body.parentID === null\r\n ? null\r\n : toCollectionID({\r\n collectionSlug,\r\n id: body.parentID,\r\n req,\r\n })\r\n const accessError = await assertUpdateAccess({\r\n collectionSlug,\r\n movedID,\r\n nextParentID,\r\n parentFieldSlug,\r\n req,\r\n })\r\n\r\n if (accessError) {\r\n return accessError\r\n }\r\n\r\n const docsResult = await req.payload.find({\r\n collection: collectionSlug as never,\r\n depth: 0,\r\n draft: collectionHasDrafts({ collectionSlug, req }) ? true : undefined,\r\n fallbackLocale: false as never,\r\n limit: 0,\r\n locale: getRequestedLocale(req) as never,\r\n overrideAccess: true,\r\n req,\r\n } as never)\r\n const docs = docsResult.docs as unknown as PageTreeSourceDoc[]\r\n const docsByID = new Map(docs.map((doc) => [stringifyDocID(doc.id), doc]))\r\n const movedDoc = docsByID.get(movedIDFromRoute)\r\n\r\n if (!movedDoc) {\r\n return respond(404, {\r\n message: 'Document not found.',\r\n })\r\n }\r\n\r\n if (body.parentID !== null && !docsByID.has(body.parentID)) {\r\n return respond(400, {\r\n message: 'Parent document not found.',\r\n })\r\n }\r\n\r\n const currentParentID = getDocParentID(movedDoc, parentFieldSlug)\r\n\r\n if (currentParentID === body.parentID) {\r\n return respond(400, {\r\n message: 'Document already has that parent.',\r\n })\r\n }\r\n\r\n if (body.parentID === movedIDFromRoute) {\r\n return respond(400, {\r\n message: CANCEL_DRAG_MESSAGE,\r\n })\r\n }\r\n\r\n const childrenByParentID = buildChildrenByParentID({\r\n docs,\r\n parentFieldSlug,\r\n })\r\n const descendantIDs = collectDescendantIDs(movedIDFromRoute, childrenByParentID)\r\n\r\n if (body.parentID !== null && descendantIDs.includes(body.parentID)) {\r\n return respond(400, {\r\n message: 'A document cannot be moved under one of its descendants.',\r\n })\r\n }\r\n\r\n await req.payload.update({\r\n collection: collectionSlug as never,\r\n context: {\r\n [pageTreeMoveContextKey]: true,\r\n },\r\n data: {\r\n [parentFieldSlug]:\r\n body.parentID === null\r\n ? null\r\n : toCollectionID({\r\n collectionSlug,\r\n id: body.parentID,\r\n req,\r\n }),\r\n } as never,\r\n depth: 0,\r\n draft: collectionHasDrafts({ collectionSlug, req }) ? true : undefined,\r\n id: movedID as never,\r\n locale: getRequestedLocale(req) as never,\r\n overrideAccess: true,\r\n req,\r\n } as never)\r\n\r\n return respond(200, {\r\n movedID: movedIDFromRoute,\r\n ok: true,\r\n parentID: body.parentID,\r\n })\r\n },\r\n method: 'post',\r\n path: '/:id/move',\r\n }\r\n}\r\n"],"names":["CANCEL_DRAG_MESSAGE","buildChildrenByParentID","collectDescendantIDs","getDocParentID","stringifyDocID","pageTreeMoveContextKey","respond","status","body","Response","json","hasOwnProperty","value","key","Object","prototype","call","normalizeID","undefined","normalizeMoveDocumentBody","incomingData","JSON","parse","Array","isArray","parentID","readBodyFromRequest","req","directBody","data","jsonBody","text","getPayloadCollection","collectionSlug","payload","collections","usesNumericID","collection","idType","customIDType","db","defaultIDType","toCollectionID","args","id","Number","getRequestedLocale","locale","collectionHasDrafts","Boolean","config","versions","drafts","assertUpdateAccess","movedID","nextParentID","parentFieldSlug","updateAccess","access","update","accessResult","message","i18n","t","matchingDocs","find","depth","draft","limit","overrideAccess","where","and","equals","docs","length","createMovePageEndpoint","handler","movedIDFromRoute","routeParams","accessError","docsResult","fallbackLocale","docsByID","Map","map","doc","movedDoc","get","has","currentParentID","childrenByParentID","descendantIDs","includes","context","ok","method","path"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/createMovePageEndpoint.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload'\n\nimport {\n DIAGNOSTICS_FLOW_CONTEXT_KEY,\n type Diagnostics,\n createFlowID,\n readMainRowSnapshot,\n} from '../utilities/diagnostics.js'\nimport { CANCEL_DRAG_MESSAGE } from '../utilities/moveValidation.js'\nimport {\n buildChildrenByParentID,\n collectDescendantIDs,\n getDocParentID,\n stringifyDocID,\n} from '../utilities/pageTree.js'\nimport {\n pageTreeMoveContextKey,\n type PageTreeSourceDoc,\n pageTreeWriteContextKey,\n} from '../types.js'\nimport {\n assertUpdateAccess,\n collectionHasAutosaveDrafts,\n collectionHasDrafts,\n getRequestedLocale,\n normalizeID,\n respond,\n toCollectionID,\n} from './shared.js'\n\nconst SNAPSHOT_FIELDS = ['_status'] as const\n\ntype MoveDocumentRequestBody = {\n parentID: null | string\n}\n\nfunction hasOwnProperty(value: object, key: PropertyKey): boolean {\n return Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction normalizeMoveDocumentBody(value: unknown): MoveDocumentRequestBody | null {\n const incomingData =\n typeof value === 'string'\n ? (() => {\n try {\n return JSON.parse(value) as unknown\n } catch {\n return null\n }\n })()\n : value\n\n if (!incomingData || typeof incomingData !== 'object' || Array.isArray(incomingData)) {\n return null\n }\n\n if (!hasOwnProperty(incomingData, 'parentID')) {\n return null\n }\n\n const parentID = normalizeID((incomingData as { parentID?: unknown }).parentID)\n\n if (\n (incomingData as { parentID?: unknown }).parentID !== null &&\n (incomingData as { parentID?: unknown }).parentID !== undefined &&\n parentID === null\n ) {\n return null\n }\n\n return {\n parentID,\n }\n}\n\ntype RawBodyAttempt = {\n raw: unknown\n source: 'req.data' | 'req.json' | 'req.text'\n}\n\nasync function readBodyFromRequest(\n req: PayloadRequest,\n): Promise<{ body: MoveDocumentRequestBody | null; lastAttempt: null | RawBodyAttempt }> {\n let lastAttempt: null | RawBodyAttempt = null\n\n if (req.data !== undefined && req.data !== null) {\n lastAttempt = { raw: req.data, source: 'req.data' }\n const directBody = normalizeMoveDocumentBody(req.data)\n\n if (directBody) {\n return { body: directBody, lastAttempt }\n }\n }\n\n if (typeof req.json === 'function') {\n try {\n const raw = await req.json()\n lastAttempt = { raw, source: 'req.json' }\n const jsonBody = normalizeMoveDocumentBody(raw)\n\n if (jsonBody) {\n return { body: jsonBody, lastAttempt }\n }\n } catch {\n // Fall through to the text-based fallback when the runtime does not hydrate req.data.\n }\n }\n\n if (typeof req.text === 'function') {\n try {\n const raw = await req.text()\n lastAttempt = { raw, source: 'req.text' }\n const textBody = normalizeMoveDocumentBody(raw)\n\n if (textBody) {\n return { body: textBody, lastAttempt }\n }\n } catch {\n return { body: null, lastAttempt }\n }\n }\n\n return { body: null, lastAttempt }\n}\n\nexport function createMovePageEndpoint(args: {\n collectionSlug: string\n diagnostics: Diagnostics\n parentFieldSlug: string\n publishOnMove?: boolean\n}): Endpoint {\n const { collectionSlug, diagnostics, parentFieldSlug, publishOnMove = false } = args\n const snapshotFields = [...SNAPSHOT_FIELDS, parentFieldSlug]\n\n return {\n handler: async (req) => {\n const movedIDFromRoute = normalizeID(req.routeParams?.id)\n\n if (!movedIDFromRoute) {\n return respond(400, {\n message: 'Document ID was not specified.',\n })\n }\n\n const { body, lastAttempt } = await readBodyFromRequest(req)\n\n if (!body) {\n if (diagnostics.enabled) {\n diagnostics.log({\n collection: collectionSlug,\n data: {\n movedID: movedIDFromRoute,\n rawBody: lastAttempt?.raw ?? null,\n rawBodySource: lastAttempt?.source ?? null,\n },\n flow: createFlowID('move-endpoint'),\n level: 'warn',\n message: 'move endpoint rejected request body: parentID was missing or malformed',\n source: 'move-endpoint:body-rejected',\n })\n }\n\n return respond(400, {\n message: 'A valid parentID is required.',\n })\n }\n\n const movedID = toCollectionID({\n collectionSlug,\n id: movedIDFromRoute,\n req,\n })\n const nextParentID =\n body.parentID === null\n ? null\n : toCollectionID({\n collectionSlug,\n id: body.parentID,\n req,\n })\n const accessError = await assertUpdateAccess({\n collectionSlug,\n data: { [parentFieldSlug]: nextParentID },\n id: movedID,\n req,\n })\n\n if (accessError) {\n return accessError\n }\n\n const docsResult = await req.payload.find({\n collection: collectionSlug as never,\n depth: 0,\n draft: collectionHasDrafts({ collectionSlug, req }) ? true : undefined,\n fallbackLocale: false as never,\n limit: 0,\n locale: getRequestedLocale(req) as never,\n overrideAccess: true,\n req,\n } as never)\n const docs = docsResult.docs as unknown as PageTreeSourceDoc[]\n const docsByID = new Map(docs.map((doc) => [stringifyDocID(doc.id), doc]))\n const movedDoc = docsByID.get(movedIDFromRoute)\n\n if (!movedDoc) {\n return respond(404, {\n message: 'Document not found.',\n })\n }\n\n if (body.parentID !== null && !docsByID.has(body.parentID)) {\n return respond(400, {\n message: 'Parent document not found.',\n })\n }\n\n const currentParentID = getDocParentID(movedDoc, parentFieldSlug)\n\n if (currentParentID === body.parentID) {\n return respond(400, {\n message: 'Document already has that parent.',\n })\n }\n\n if (body.parentID === movedIDFromRoute) {\n return respond(400, {\n message: CANCEL_DRAG_MESSAGE,\n })\n }\n\n const childrenByParentID = buildChildrenByParentID({\n docs,\n parentFieldSlug,\n })\n const descendantIDs = collectDescendantIDs(movedIDFromRoute, childrenByParentID)\n\n if (body.parentID !== null && descendantIDs.includes(body.parentID)) {\n return respond(400, {\n message: 'A document cannot be moved under one of its descendants.',\n })\n }\n\n const flow = createFlowID('move-endpoint')\n const moveStart = Date.now()\n const hasDrafts = collectionHasDrafts({ collectionSlug, req })\n const hasAutosave = hasDrafts && collectionHasAutosaveDrafts({ collectionSlug, req })\n // With `publishOnMove`, publish the reparent right away instead of staging\n // it — but ONLY when the moved doc had nothing staged before the move (its\n // latest version, read above with `draft: true`, is already published). A\n // doc with pending draft edits stays staged, so those in-progress edits are\n // never published as a side effect of the move.\n const publishMove = publishOnMove && hasDrafts && movedDoc._status === 'published'\n const updateData = {\n [parentFieldSlug]:\n body.parentID === null\n ? null\n : toCollectionID({\n collectionSlug,\n id: body.parentID,\n req,\n }),\n ...(publishMove ? { _status: 'published' } : {}),\n }\n const updateArgs = {\n collection: collectionSlug as never,\n context: {\n // A published move changes the live URL of this page and of every\n // descendant nested-docs resaves on this request, so the deploy\n // opt-out flag is withheld and consumer rebuild hooks fire normally.\n // A staged move changes nothing live and keeps the flag.\n ...(publishMove ? {} : { [pageTreeMoveContextKey]: true }),\n [pageTreeWriteContextKey]: true,\n } as Record<string, unknown>,\n autosave: hasAutosave && !publishMove ? true : undefined,\n data: updateData as never,\n depth: 0,\n draft: hasDrafts && !publishMove ? true : undefined,\n id: movedID as never,\n locale: getRequestedLocale(req) as never,\n overrideAccess: true,\n req,\n }\n\n if (diagnostics.enabled) {\n if (!req.context) {\n req.context = {}\n }\n\n ;(req.context as Record<string, unknown>)[DIAGNOSTICS_FLOW_CONTEXT_KEY] = flow\n\n const beforeSnapshot = await readMainRowSnapshot({\n collectionSlug,\n fields: snapshotFields,\n id: movedID,\n req,\n })\n\n diagnostics.log({\n collection: collectionSlug,\n data: {\n autosave: updateArgs.autosave ?? null,\n currentParentID,\n draft: updateArgs.draft,\n hasTransaction: Boolean((req as { transactionID?: unknown }).transactionID),\n locale: updateArgs.locale ?? null,\n movedDocStatus: movedDoc._status ?? null,\n movedID: String(movedID),\n nextParentID: body.parentID,\n publishedMainRowBefore: beforeSnapshot,\n publishMove,\n updateData,\n },\n flow,\n level: 'info',\n message: 'move endpoint entering: parent move only',\n source: 'move-endpoint:enter',\n })\n }\n\n try {\n const result = (await req.payload.update(updateArgs as never)) as\n | (Record<string, unknown> & { _status?: string })\n | undefined\n\n if (diagnostics.enabled) {\n const afterSnapshot = await readMainRowSnapshot({\n collectionSlug,\n fields: snapshotFields,\n id: movedID,\n req,\n })\n\n diagnostics.log({\n collection: collectionSlug,\n data: {\n durMs: Date.now() - moveStart,\n movedID: String(movedID),\n publishedMainRowAfter: afterSnapshot,\n resultStatus: result?._status ?? null,\n },\n flow,\n level: 'info',\n message: 'move endpoint payload.update succeeded',\n source: 'move-endpoint:ok',\n })\n }\n } catch (err) {\n const error = err as { data?: unknown; message?: string; name?: string } & Error\n\n diagnostics.log({\n collection: collectionSlug,\n data: {\n data: error?.data ?? null,\n durMs: Date.now() - moveStart,\n message: error?.message,\n movedID: String(movedID),\n name: error?.name,\n },\n flow,\n level: 'error',\n message: `move endpoint payload.update failed: ${error?.message ?? 'unknown error'}`,\n source: 'move-endpoint:error',\n })\n\n throw err\n }\n\n return respond(200, {\n movedID: movedIDFromRoute,\n ok: true,\n parentID: body.parentID,\n })\n },\n method: 'post',\n path: '/:id/move',\n }\n}\n"],"names":["DIAGNOSTICS_FLOW_CONTEXT_KEY","createFlowID","readMainRowSnapshot","CANCEL_DRAG_MESSAGE","buildChildrenByParentID","collectDescendantIDs","getDocParentID","stringifyDocID","pageTreeMoveContextKey","pageTreeWriteContextKey","assertUpdateAccess","collectionHasAutosaveDrafts","collectionHasDrafts","getRequestedLocale","normalizeID","respond","toCollectionID","SNAPSHOT_FIELDS","hasOwnProperty","value","key","Object","prototype","call","normalizeMoveDocumentBody","incomingData","JSON","parse","Array","isArray","parentID","undefined","readBodyFromRequest","req","lastAttempt","data","raw","source","directBody","body","json","jsonBody","text","textBody","createMovePageEndpoint","args","collectionSlug","diagnostics","parentFieldSlug","publishOnMove","snapshotFields","handler","movedIDFromRoute","routeParams","id","message","enabled","log","collection","movedID","rawBody","rawBodySource","flow","level","nextParentID","accessError","docsResult","payload","find","depth","draft","fallbackLocale","limit","locale","overrideAccess","docs","docsByID","Map","map","doc","movedDoc","get","has","currentParentID","childrenByParentID","descendantIDs","includes","moveStart","Date","now","hasDrafts","hasAutosave","publishMove","_status","updateData","updateArgs","context","autosave","beforeSnapshot","fields","hasTransaction","Boolean","transactionID","movedDocStatus","String","publishedMainRowBefore","result","update","afterSnapshot","durMs","publishedMainRowAfter","resultStatus","err","error","name","ok","method","path"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload';
|
|
2
|
+
import { type Diagnostics } from '../utilities/diagnostics.js';
|
|
3
|
+
export declare function createReorderPageEndpoint(args: {
|
|
4
|
+
collectionSlug: string;
|
|
5
|
+
diagnostics: Diagnostics;
|
|
6
|
+
orderableFieldName: string;
|
|
7
|
+
parentFieldSlug: string;
|
|
8
|
+
}): Endpoint;
|