@payloadcms/figma 0.1.0-alpha.4 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db-content-api/index.js +31 -17
- package/dist/db-content-api/utilities/joins.js +5 -1
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.d.ts +10 -0
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.js +95 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.d.ts +9 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.js +112 -0
- package/dist/db-content-api/utilities/where.d.ts +2 -6
- package/dist/db-content-api/utilities/where.js +20 -11
- package/dist/exports/views.d.ts +1 -0
- package/dist/exports/views.js +1 -0
- package/dist/imageSizeOptions.d.ts +23 -0
- package/dist/imageSizeOptions.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/oauth/defaults.d.ts +1 -0
- package/dist/oauth/defaults.js +1 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +7 -2
- package/dist/oauth/index.js +24 -9
- package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +1 -1
- package/dist/oauth/utilities/getAuthorizeURL.d.ts +3 -0
- package/dist/oauth/utilities/getAuthorizeURL.js +8 -2
- package/dist/plugin/build-config.js +22 -9
- package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +2 -3
- package/dist/plugin/cloud-limits/CloudLimitModal/index.js +4 -16
- package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +5 -0
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +0 -3
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +65 -124
- package/dist/plugin/cloud-limits/LimitReachedView/index.css +14 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.d.ts +4 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.js +52 -0
- package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +7 -2
- package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +0 -8
- package/dist/plugin/cloud-limits/SlugTracker/index.js +14 -10
- package/dist/plugin/cloud-limits/routes.d.ts +2 -0
- package/dist/plugin/cloud-limits/routes.js +4 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.d.ts +9 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.js +28 -0
- package/dist/plugin/cloud-limits/shared.d.ts +1 -1
- package/dist/plugin/cloud-limits/shared.js +25 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.d.ts +2 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.js +134 -0
- package/dist/plugin/cloud-limits/utilities/toasts.d.ts +4 -0
- package/dist/plugin/cloud-limits/utilities/toasts.js +19 -0
- package/package.json +8 -1
- package/dist/db-content-api/utilities/data/stringifyNumericIds.d.ts +0 -1
- package/dist/db-content-api/utilities/data/stringifyNumericIds.js +0 -19
|
@@ -3,6 +3,7 @@ import { APIError, createDatabaseAdapter, ValidationError } from 'payload';
|
|
|
3
3
|
import { v4 as uuid } from 'uuid';
|
|
4
4
|
import { FigmaApiError } from '../api/figma-api.js';
|
|
5
5
|
import { CONTENT_SYSTEM_NOT_FOUND_MESSAGE } from '../constants.js';
|
|
6
|
+
import { redirectInitialAutosaveCreate } from '../plugin/cloud-limits/server/redirectInitialAutosaveCreate.js';
|
|
6
7
|
import { getGlobalSlug } from './temp-utilities/slug.js';
|
|
7
8
|
import { addFallbackSort } from './temp-utilities/sorting.js';
|
|
8
9
|
import { unwrapDocument, unwrapFindResponse } from './temp-utilities/unwrapDocument.js';
|
|
@@ -122,7 +123,7 @@ async function createCollection(collectionId) {
|
|
|
122
123
|
}
|
|
123
124
|
async function findMany({ collection, joins, limit, locale: localeArg, page, pagination: _pagination, sort, where }) {
|
|
124
125
|
const locale = addFallbackLocale(localeArg, this.payload);
|
|
125
|
-
const
|
|
126
|
+
const localeNormalizedWhere = normalizeLocaleInWhere(where, this.payload, collection, locale);
|
|
126
127
|
const joinClause = convertPayloadJoinsToContentAPI(this.payload, collection, joins);
|
|
127
128
|
const { data: response, error, response: httpResponse } = await this.client.POST('/api/v1/documents:find', {
|
|
128
129
|
body: {
|
|
@@ -134,7 +135,7 @@ async function findMany({ collection, joins, limit, locale: localeArg, page, pag
|
|
|
134
135
|
page: page ?? 1,
|
|
135
136
|
schema: this.documentSchema(),
|
|
136
137
|
sort: addFallbackSort(sort, this.payload, collection),
|
|
137
|
-
where: convertPayloadWhereToContentAPI(
|
|
138
|
+
where: convertPayloadWhereToContentAPI(localeNormalizedWhere, this.payload, collection, false)
|
|
138
139
|
}
|
|
139
140
|
});
|
|
140
141
|
if (error) {
|
|
@@ -166,7 +167,7 @@ async function find(args) {
|
|
|
166
167
|
// TODO: remove Omit when this PR is released: https://github.com/payloadcms/payload/pull/15183
|
|
167
168
|
async function findVersions(args) {
|
|
168
169
|
const sortWithFallback = addFallbackSort(args.sort || '-updatedAt', this.payload, args.collection);
|
|
169
|
-
const whereClause = convertPayloadWhereToContentAPI(args.where
|
|
170
|
+
const whereClause = convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, true);
|
|
170
171
|
const locale = addFallbackLocale(args.locale, this.payload);
|
|
171
172
|
const { data: response, error, response: httpResponse } = await this.client.POST('/api/v1/document_versions:find', {
|
|
172
173
|
body: {
|
|
@@ -281,7 +282,7 @@ async function updateVersion(args) {
|
|
|
281
282
|
updatedAt: versionMeta.updatedAt,
|
|
282
283
|
version: versionContent
|
|
283
284
|
},
|
|
284
|
-
where: convertPayloadWhereToContentAPI(where)
|
|
285
|
+
where: convertPayloadWhereToContentAPI(where, this.payload, args.collection, true)
|
|
285
286
|
}
|
|
286
287
|
});
|
|
287
288
|
if (error) {
|
|
@@ -316,7 +317,7 @@ async function deleteVersions(args) {
|
|
|
316
317
|
contentSystemId: this.contentSystemId,
|
|
317
318
|
locale,
|
|
318
319
|
schema: this.documentSchema(),
|
|
319
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
320
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, collection, true)
|
|
320
321
|
}
|
|
321
322
|
});
|
|
322
323
|
if (error) {
|
|
@@ -346,7 +347,7 @@ async function findDistinct(args) {
|
|
|
346
347
|
page: args.page ?? 1,
|
|
347
348
|
schema: this.documentSchema(),
|
|
348
349
|
sort: addFallbackSort(args.sort, this.payload, args.collection),
|
|
349
|
-
where: convertPayloadWhereToContentAPI(args.where
|
|
350
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
350
351
|
}
|
|
351
352
|
});
|
|
352
353
|
if (error) {
|
|
@@ -379,7 +380,7 @@ async function updateMany(args) {
|
|
|
379
380
|
locale,
|
|
380
381
|
schema: this.documentSchema(),
|
|
381
382
|
sort: addFallbackSort(args.sort || '-updatedAt', this.payload, args.collection),
|
|
382
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
383
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
383
384
|
}
|
|
384
385
|
});
|
|
385
386
|
if (error) {
|
|
@@ -419,7 +420,7 @@ async function updateOne(args) {
|
|
|
419
420
|
environmentName: this.environmentName,
|
|
420
421
|
locale,
|
|
421
422
|
schema: this.documentSchema(),
|
|
422
|
-
where: convertPayloadWhereToContentAPI(where)
|
|
423
|
+
where: convertPayloadWhereToContentAPI(where, this.payload, args.collection, false)
|
|
423
424
|
}
|
|
424
425
|
});
|
|
425
426
|
if (error) {
|
|
@@ -451,7 +452,7 @@ async function deleteMany(args) {
|
|
|
451
452
|
environmentName: this.environmentName,
|
|
452
453
|
locale,
|
|
453
454
|
schema: this.documentSchema(),
|
|
454
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
455
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
455
456
|
}
|
|
456
457
|
});
|
|
457
458
|
if (error) {
|
|
@@ -467,7 +468,7 @@ async function deleteOne(args) {
|
|
|
467
468
|
environmentName: this.environmentName,
|
|
468
469
|
locale,
|
|
469
470
|
schema: this.documentSchema(),
|
|
470
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
471
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
471
472
|
}
|
|
472
473
|
});
|
|
473
474
|
if (error) {
|
|
@@ -525,6 +526,12 @@ async function create(args) {
|
|
|
525
526
|
}
|
|
526
527
|
});
|
|
527
528
|
if (error) {
|
|
529
|
+
redirectInitialAutosaveCreate({
|
|
530
|
+
collectionSlug: args.collection,
|
|
531
|
+
errorCode: error.code,
|
|
532
|
+
payload: this.payload,
|
|
533
|
+
req: args.req
|
|
534
|
+
});
|
|
528
535
|
throw createDocumentContentAPIError(this, 'create', error, httpResponse.status, args.collection);
|
|
529
536
|
}
|
|
530
537
|
if (!contentAPIResponse) {
|
|
@@ -574,7 +581,7 @@ async function count(args) {
|
|
|
574
581
|
contentSystemId: this.contentSystemId,
|
|
575
582
|
locale,
|
|
576
583
|
schema: this.documentSchema(),
|
|
577
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
584
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
578
585
|
}
|
|
579
586
|
});
|
|
580
587
|
if (error) {
|
|
@@ -595,7 +602,7 @@ async function countVersions(args) {
|
|
|
595
602
|
contentSystemId: this.contentSystemId,
|
|
596
603
|
locale,
|
|
597
604
|
schema: this.documentSchema(),
|
|
598
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
605
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, true)
|
|
599
606
|
}
|
|
600
607
|
});
|
|
601
608
|
if (error) {
|
|
@@ -608,12 +615,19 @@ async function countVersions(args) {
|
|
|
608
615
|
totalDocs: response.result.count
|
|
609
616
|
};
|
|
610
617
|
}
|
|
618
|
+
// Content API document ids are text. A document id is only ever a string or a number
|
|
619
|
+
// (a custom numeric id), so coerce those to a string and treat anything else as absent.
|
|
620
|
+
function toIdString(value) {
|
|
621
|
+
return typeof value === 'string' || typeof value === 'number' ? String(value) : undefined;
|
|
622
|
+
}
|
|
611
623
|
async function upsert(args) {
|
|
612
|
-
|
|
624
|
+
// The Content API stores every document id as text; a custom numeric id arrives as a
|
|
625
|
+
// number, so coerce it (matching `create`) before it becomes `createOnMissing.id`.
|
|
626
|
+
let documentId = toIdString(args.data.id);
|
|
613
627
|
if (!documentId && args.where && 'id' in args.where) {
|
|
614
628
|
const idClause = args.where.id;
|
|
615
|
-
if (idClause
|
|
616
|
-
documentId = idClause.equals;
|
|
629
|
+
if (idClause) {
|
|
630
|
+
documentId = toIdString(idClause.equals);
|
|
617
631
|
}
|
|
618
632
|
}
|
|
619
633
|
if (!documentId) {
|
|
@@ -645,7 +659,7 @@ async function upsert(args) {
|
|
|
645
659
|
environmentName: this.environmentName,
|
|
646
660
|
locale,
|
|
647
661
|
schema: this.documentSchema(),
|
|
648
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
662
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
649
663
|
}
|
|
650
664
|
});
|
|
651
665
|
if (error) {
|
|
@@ -692,7 +706,7 @@ async function updateJobs(args) {
|
|
|
692
706
|
} : where;
|
|
693
707
|
const docData = dataToContentAPI(this.payload, collection, data);
|
|
694
708
|
const sortClause = addFallbackSort(sort || '-updatedAt', this.payload, collection);
|
|
695
|
-
const whereQuery = convertPayloadWhereToContentAPI(whereClause);
|
|
709
|
+
const whereQuery = convertPayloadWhereToContentAPI(whereClause, this.payload, collection, false);
|
|
696
710
|
const { data: response, error, response: httpResponse } = await this.client.POST('/api/v1/documents:update', {
|
|
697
711
|
body: {
|
|
698
712
|
collection,
|
|
@@ -102,6 +102,10 @@ import { convertPayloadWhereToContentAPI } from './where.js';
|
|
|
102
102
|
const effectiveLimit = joinQuery.limit ?? foundJoin.defaultLimit;
|
|
103
103
|
// Add fallback sort for the joined collection
|
|
104
104
|
const sortWithFallback = addFallbackSort(joinQuery.sort, payload, foundJoin.collectionSlug, foundJoin.defaultSort);
|
|
105
|
+
// A join's `where` filters the *joined* collection, so id coercion resolves against
|
|
106
|
+
// that collection (the first target for a polymorphic join; `id` is coerced regardless).
|
|
107
|
+
const joinedSlug = Array.isArray(foundJoin.collectionSlug) ? foundJoin.collectionSlug[0] : foundJoin.collectionSlug;
|
|
108
|
+
const whereConverted = convertPayloadWhereToContentAPI(joinQuery.where, payload, joinedSlug, false);
|
|
105
109
|
const contentAPIJoin = {
|
|
106
110
|
count: joinQuery.count ?? false,
|
|
107
111
|
path: joinPath,
|
|
@@ -115,7 +119,7 @@ import { convertPayloadWhereToContentAPI } from './where.js';
|
|
|
115
119
|
sort: sortWithFallback
|
|
116
120
|
},
|
|
117
121
|
...joinQuery.where && {
|
|
118
|
-
where:
|
|
122
|
+
where: whereConverted
|
|
119
123
|
}
|
|
120
124
|
};
|
|
121
125
|
result.push(contentAPIJoin);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Field, Payload } from 'payload';
|
|
2
|
+
export interface ResolveContext {
|
|
3
|
+
readonly fieldCache: Map<string, boolean>;
|
|
4
|
+
readonly fields: Field[];
|
|
5
|
+
readonly isVersions: boolean;
|
|
6
|
+
readonly payload: Payload;
|
|
7
|
+
}
|
|
8
|
+
/** Walks a path's segments against a field list, reporting whether it lands on a
|
|
9
|
+
* relationship/upload field's id-bearing value. */
|
|
10
|
+
export declare function resolvesToRelationshipValue(segments: string[], fields: Field[], ctx: ResolveContext): boolean;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { resolveBlocks } from '../resolveBlocks.js';
|
|
2
|
+
/** Walks a path's segments against a field list, reporting whether it lands on a
|
|
3
|
+
* relationship/upload field's id-bearing value. */ export function resolvesToRelationshipValue(segments, fields, ctx) {
|
|
4
|
+
const [head, ...rest] = segments;
|
|
5
|
+
if (head == null) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const field = findNamedField(fields, head);
|
|
9
|
+
if (field == null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
switch(field.type){
|
|
13
|
+
case 'array':
|
|
14
|
+
return 'fields' in field && resolvesToRelationshipValue(skipLocaleSegment(rest, field, ctx), field.fields, ctx);
|
|
15
|
+
case 'blocks':
|
|
16
|
+
{
|
|
17
|
+
if (!('blocks' in field)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
// Any block whose fields the remaining path resolves through makes it id-bearing:
|
|
21
|
+
// one path can only mean one thing, whichever block supplied the field.
|
|
22
|
+
const childSegments = skipLocaleSegment(rest, field, ctx);
|
|
23
|
+
return resolveBlocks(field.blocks, ctx.payload.config.blocks).some((block)=>resolvesToRelationshipValue(childSegments, block.fields, ctx));
|
|
24
|
+
}
|
|
25
|
+
case 'group':
|
|
26
|
+
return 'fields' in field && resolvesToRelationshipValue(rest, field.fields, ctx);
|
|
27
|
+
case 'relationship':
|
|
28
|
+
case 'upload':
|
|
29
|
+
// A polymorphic field's id sits at `<path>.value`; its `.relationTo` is a slug.
|
|
30
|
+
if (Array.isArray(field.relationTo)) {
|
|
31
|
+
return rest.length === 1 && rest[0] === 'value';
|
|
32
|
+
}
|
|
33
|
+
// A single-target field's id sits at the field's own path.
|
|
34
|
+
if (rest.length === 0) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// Payload accepts an explicit `<path>.id` for that same value —
|
|
38
|
+
// `getTableColumnFromPath` treats the bare path and `.id` as one column — which the
|
|
39
|
+
// Content API resolves as a traversal into the related collection's, equally text, id.
|
|
40
|
+
return rest.length === 1 && rest[0] === 'id';
|
|
41
|
+
default:
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Finds the field a path segment names. Unnamed containers (row, collapsible, unnamed tab)
|
|
46
|
+
* are transparent to query paths, so their children are searched too; a named tab nests its
|
|
47
|
+
* fields under its own name, exactly like a group. */ function findNamedField(fields, name) {
|
|
48
|
+
for (const field of fields){
|
|
49
|
+
if ('name' in field && field.name) {
|
|
50
|
+
// A named container's children need its name consumed first, so only match the
|
|
51
|
+
// container itself here.
|
|
52
|
+
if (field.name === name) {
|
|
53
|
+
return field;
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if ('fields' in field && Array.isArray(field.fields)) {
|
|
58
|
+
const match = findNamedField(field.fields, name);
|
|
59
|
+
if (match) {
|
|
60
|
+
return match;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if ('tabs' in field && Array.isArray(field.tabs)) {
|
|
64
|
+
for (const tab of field.tabs){
|
|
65
|
+
if (!tab.fields) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!tab.name) {
|
|
69
|
+
const match = findNamedField(tab.fields, name);
|
|
70
|
+
if (match) {
|
|
71
|
+
return match;
|
|
72
|
+
}
|
|
73
|
+
} else if (tab.name === name) {
|
|
74
|
+
return {
|
|
75
|
+
name: tab.name,
|
|
76
|
+
type: 'group',
|
|
77
|
+
fields: tab.fields
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
// A localized array/blocks field is stored as a locale map, so its children's paths carry a
|
|
86
|
+
// locale key (`items.en.rel`) — see `normalizeLocaleInWhere`, which runs before this.
|
|
87
|
+
// Locale keys are *only* added to `array` and `blocks`.
|
|
88
|
+
function skipLocaleSegment(segments, field, ctx) {
|
|
89
|
+
const localization = ctx.payload.config.localization;
|
|
90
|
+
if (!localization || !('localized' in field) || !field.localized) {
|
|
91
|
+
return segments;
|
|
92
|
+
}
|
|
93
|
+
const [head] = segments;
|
|
94
|
+
return head != null && localization.localeCodes.includes(head) ? segments.slice(1) : segments;
|
|
95
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Payload, Where } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Stringifies numeric operands on a Where clause's id-bearing paths (`id`, `parent` for
|
|
4
|
+
* versions, and relationship-value paths), so a custom-numeric-id filter matches the
|
|
5
|
+
* Content API's text storage. Returns the clause untouched when there is nothing to
|
|
6
|
+
* convert. `isVersions` treats `parent` (the parent document's id) as id-bearing and looks
|
|
7
|
+
* for the collection's own fields under `version.`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function stringifyIdsInWhere(where: undefined | Where, payload: Payload, collectionSlug: string, isVersions: boolean): undefined | Where;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { resolvesToRelationshipValue } from './resolvesToRelationshipValue.js';
|
|
2
|
+
// The Content API stores every document id and every relationship value as text, but
|
|
3
|
+
// Payload keeps a collection's custom numeric id as a `number`. So a filter that carries
|
|
4
|
+
// a numeric id would never match the stored string form. This is the sole place the
|
|
5
|
+
// adapter translates those query-side ids to the wire's string form (the data-side
|
|
6
|
+
// counterpart is `convertRelationshipIds`); `convertPayloadWhereToContentAPI` itself is
|
|
7
|
+
// purely structural.
|
|
8
|
+
//
|
|
9
|
+
// A condition's path is id-bearing when it resolves to:
|
|
10
|
+
// - `id` — the document's own id (always).
|
|
11
|
+
// - `parent` — the parent document's id, on a *versions* query (`isVersions`).
|
|
12
|
+
// - a relationship/upload field's value: the field's own path for a single-target field
|
|
13
|
+
// (`rel: { equals: 999 }`), or `<path>.value` for a polymorphic one
|
|
14
|
+
// (`rel.value: { equals: 999 }`). A polymorphic field's `.relationTo` is a slug, not
|
|
15
|
+
// an id, so it is deliberately left alone.
|
|
16
|
+
//
|
|
17
|
+
// Every stored id is text, so a bare "stringify any numeric operand on this path" is
|
|
18
|
+
// sufficient — no per-target id-type resolution is needed (which also handles a
|
|
19
|
+
// polymorphic `in` array that mixes targets).
|
|
20
|
+
/**
|
|
21
|
+
* Stringifies numeric operands on a Where clause's id-bearing paths (`id`, `parent` for
|
|
22
|
+
* versions, and relationship-value paths), so a custom-numeric-id filter matches the
|
|
23
|
+
* Content API's text storage. Returns the clause untouched when there is nothing to
|
|
24
|
+
* convert. `isVersions` treats `parent` (the parent document's id) as id-bearing and looks
|
|
25
|
+
* for the collection's own fields under `version.`.
|
|
26
|
+
*/ export function stringifyIdsInWhere(where, payload, collectionSlug, isVersions) {
|
|
27
|
+
if (!where) {
|
|
28
|
+
return where;
|
|
29
|
+
}
|
|
30
|
+
// An unknown slug names no fields, but `id` (and `parent` on a versions query) is
|
|
31
|
+
// id-bearing regardless of the config, so still walk the clause with an empty field list:
|
|
32
|
+
// discarding it here would turn the query into a match-all.
|
|
33
|
+
const config = payload.config.collections.find((c)=>c.slug === collectionSlug);
|
|
34
|
+
const fields = config?.fields ?? [];
|
|
35
|
+
const fieldCache = new Map();
|
|
36
|
+
return transformWhere(where, {
|
|
37
|
+
fieldCache,
|
|
38
|
+
fields,
|
|
39
|
+
isVersions,
|
|
40
|
+
payload
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Recursively transforms a Where clause, stringifying numeric operands on any condition
|
|
44
|
+
* whose path is id-bearing. */ function transformWhere(where, context) {
|
|
45
|
+
const result = {};
|
|
46
|
+
for (const [key, value] of Object.entries(where)){
|
|
47
|
+
const keyLower = key.toLowerCase();
|
|
48
|
+
if (keyLower === 'and' || keyLower === 'or') {
|
|
49
|
+
const targetValue = where[key];
|
|
50
|
+
if (targetValue == null || !Array.isArray(targetValue)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
result[key] = targetValue.map((clause)=>transformWhere(clause, context));
|
|
54
|
+
} else if (isIdBearingPath(key, context)) {
|
|
55
|
+
result[key] = stringifyNumericOperands(value);
|
|
56
|
+
} else {
|
|
57
|
+
result[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/** Whether a condition's path resolves to an id. */ function isIdBearingPath(path, context) {
|
|
63
|
+
const cachedResult = context.fieldCache.get(path);
|
|
64
|
+
if (cachedResult != null) {
|
|
65
|
+
return cachedResult;
|
|
66
|
+
}
|
|
67
|
+
// GraphQL spells a nested path with `__`, since dots are invalid in GraphQL names; the
|
|
68
|
+
// structural conversion normalizes those the same way.
|
|
69
|
+
const segments = path.replace(/__/g, '.').split('.');
|
|
70
|
+
// The document's own id — on a versions query, the version document's own id.
|
|
71
|
+
if (segments.length === 1 && segments[0] === 'id') {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (context.isVersions) {
|
|
75
|
+
// `parent` is a version-only field that contains an ID.
|
|
76
|
+
if (segments.length === 1) {
|
|
77
|
+
return segments[0] === 'parent';
|
|
78
|
+
}
|
|
79
|
+
// The only other paths that can contain IDs are in the version envelope.
|
|
80
|
+
if (segments[0] !== 'version') {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Normalize the path segments by removing the `version` envelope for paths
|
|
85
|
+
// in document versions.
|
|
86
|
+
const normalizedSegments = context.isVersions ? segments.slice(1) : segments;
|
|
87
|
+
return resolvesToRelationshipValue(normalizedSegments, context.fields, context);
|
|
88
|
+
}
|
|
89
|
+
function stringifyNumericOperands(condition) {
|
|
90
|
+
// Payload's shorthand — `{ id: 999 }` rather than `{ id: { equals: 999 } }` — carries the
|
|
91
|
+
// operand directly, with no operator keys to walk. The structural conversion reads it as
|
|
92
|
+
// an implicit `equals`, so coerce it in place: walking a primitive as an operator map
|
|
93
|
+
// would drop the condition entirely and turn the query into a match-all.
|
|
94
|
+
if (condition === null || typeof condition !== 'object' || Array.isArray(condition)) {
|
|
95
|
+
return stringifyNumbers(condition);
|
|
96
|
+
}
|
|
97
|
+
const result = {};
|
|
98
|
+
for (const [operator, operand] of Object.entries(condition)){
|
|
99
|
+
result[operator] = stringifyNumbers(operand);
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
/** A number becomes its string form; a number inside an array does too (e.g. `in`);
|
|
104
|
+
* everything else passes through untouched. */ function stringifyNumbers(value) {
|
|
105
|
+
if (typeof value === 'number') {
|
|
106
|
+
return String(value);
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return value.map((entry)=>typeof entry === 'number' ? String(entry) : entry);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import type { Where } from 'payload';
|
|
1
|
+
import type { Payload, Where } from 'payload';
|
|
2
2
|
import type { components } from '../generated/content-api-types.js';
|
|
3
3
|
type WhereClauseV1 = components['schemas']['WhereClauseV1'];
|
|
4
|
-
|
|
5
|
-
/** Internal flag to track if we're inside a logical operator (and/or) */
|
|
6
|
-
insideLogicalOperator?: boolean;
|
|
7
|
-
};
|
|
8
|
-
export declare function convertPayloadWhereToContentAPI(where: undefined | Where, options?: ConvertOptions): WhereClauseV1;
|
|
4
|
+
export declare function convertPayloadWhereToContentAPI(where: undefined | Where, payload: Payload, collectionSlug: string, isVersions: boolean): WhereClauseV1;
|
|
9
5
|
export {};
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { stringifyIdsInWhere } from './meta/stringifyIdsInWhere.js';
|
|
2
|
+
// Payload's format: { fieldName: { operator: value }, and: [...], or: [...] }
|
|
3
|
+
// Content API expects: { and: [{ path, operator, value }], or: [{ path, operator, value }] }
|
|
4
|
+
//
|
|
5
|
+
// The format stays different. This allows the Content API to evolve its filter schema,
|
|
6
|
+
// for example to support composite types like Point, which are not lexicographically sortable.
|
|
7
|
+
// Semantics must remain identical to Payload.
|
|
8
|
+
export function convertPayloadWhereToContentAPI(where, payload, collectionSlug, isVersions) {
|
|
9
|
+
// Normalize id-bearing operands to the wire's string form (custom numeric ids), then
|
|
10
|
+
// convert Payload's where shape to the Content API's. The two steps are inseparable —
|
|
11
|
+
// every id filter must be coerced — so they live behind this single entry point.
|
|
12
|
+
return convertStructure(stringifyIdsInWhere(where, payload, collectionSlug, isVersions), {
|
|
13
|
+
insideLogicalOperator: false
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
// The structural Payload → Content API conversion. Assumes id operands have already been
|
|
17
|
+
// coerced to the wire's string form (see `convertPayloadWhereToContentAPI`).
|
|
18
|
+
function convertStructure(where, options) {
|
|
3
19
|
// Empty where {} should be { and: [] } not undefined
|
|
4
20
|
// Content API requires a where clause structure even for "no filter"
|
|
5
21
|
if (!where || Object.keys(where).length === 0) {
|
|
@@ -11,7 +27,7 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
11
27
|
for (const [key, value] of Object.entries(where)){
|
|
12
28
|
const keyLower = key.toLowerCase();
|
|
13
29
|
if (keyLower === 'and' || keyLower === 'or') {
|
|
14
|
-
const nestedConditions = value.map((item)=>
|
|
30
|
+
const nestedConditions = value.map((item)=>convertStructure(item, {
|
|
15
31
|
...options,
|
|
16
32
|
insideLogicalOperator: true
|
|
17
33
|
})).filter((condition)=>{
|
|
@@ -43,7 +59,7 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
43
59
|
conditions.push({
|
|
44
60
|
operator: 'equals',
|
|
45
61
|
path: fieldPath,
|
|
46
|
-
value
|
|
62
|
+
value
|
|
47
63
|
});
|
|
48
64
|
continue;
|
|
49
65
|
}
|
|
@@ -68,13 +84,6 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
68
84
|
if (finalValue === 'null') {
|
|
69
85
|
finalValue = null;
|
|
70
86
|
}
|
|
71
|
-
// Content API stores all document IDs as strings.
|
|
72
|
-
// Payload sends numeric values for collections with custom numeric ID fields,
|
|
73
|
-
// so we must stringify to match. This includes the `in`/`not_in` arrays that
|
|
74
|
-
// Payload's relationship-population dataloader always uses ({ id: { in: [...] } }).
|
|
75
|
-
if (fieldPath === 'id') {
|
|
76
|
-
finalValue = stringifyNumericIds(finalValue);
|
|
77
|
-
}
|
|
78
87
|
conditions.push({
|
|
79
88
|
operator: op,
|
|
80
89
|
path: fieldPath,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LimitReachedView } from '../plugin/cloud-limits/LimitReachedView/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LimitReachedView } from '../plugin/cloud-limits/LimitReachedView/index.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RequestInitCfPropertiesImage } from '@cloudflare/workers-types';
|
|
2
|
+
type CloudflareWorkerImageOptions = RequestInitCfPropertiesImage;
|
|
3
|
+
export declare const supportedCloudflareImageOptionKeys: readonly ["anim", "background", "blur", "border", "brightness", "compression", "contrast", "dpr", "fit", "flip", "format", "gamma", "gravity", "height", "metadata", "quality", "rotate", "saturation", "segment", "sharpen", "trim", "upscale", "width", "zoom"];
|
|
4
|
+
type SupportedCloudflareImageOptionKey = (typeof supportedCloudflareImageOptionKeys)[number];
|
|
5
|
+
type SupportedCloudflareImageOptions = Pick<CloudflareWorkerImageOptions, SupportedCloudflareImageOptionKey>;
|
|
6
|
+
type CloudflareImageSizeFormat = Exclude<NonNullable<SupportedCloudflareImageOptions['format']>, 'json' | 'svg'>;
|
|
7
|
+
type CloudflareImageSizeOptionsExceptFormat = Omit<SupportedCloudflareImageOptions, 'format'>;
|
|
8
|
+
/**
|
|
9
|
+
* Options accepted for a reusable Payload image-size preset.
|
|
10
|
+
*
|
|
11
|
+
* Presets can control the generated image, but cannot use remote overlays or
|
|
12
|
+
* forward authentication to an image origin. Formats that do not produce a
|
|
13
|
+
* supported stored image are also excluded.
|
|
14
|
+
*/
|
|
15
|
+
export type CloudflareImageSizeOptions = {
|
|
16
|
+
format?: CloudflareImageSizeFormat;
|
|
17
|
+
} & CloudflareImageSizeOptionsExceptFormat;
|
|
18
|
+
declare module 'payload' {
|
|
19
|
+
interface RegisteredImageSizeOptions {
|
|
20
|
+
figma: CloudflareImageSizeOptions;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const supportedCloudflareImageOptionKeys = [
|
|
2
|
+
'anim',
|
|
3
|
+
'background',
|
|
4
|
+
'blur',
|
|
5
|
+
'border',
|
|
6
|
+
'brightness',
|
|
7
|
+
'compression',
|
|
8
|
+
'contrast',
|
|
9
|
+
'dpr',
|
|
10
|
+
'fit',
|
|
11
|
+
'flip',
|
|
12
|
+
'format',
|
|
13
|
+
'gamma',
|
|
14
|
+
'gravity',
|
|
15
|
+
'height',
|
|
16
|
+
'metadata',
|
|
17
|
+
'quality',
|
|
18
|
+
'rotate',
|
|
19
|
+
'saturation',
|
|
20
|
+
'segment',
|
|
21
|
+
'sharpen',
|
|
22
|
+
'trim',
|
|
23
|
+
'upscale',
|
|
24
|
+
'width',
|
|
25
|
+
'zoom'
|
|
26
|
+
];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { contentAPIAdapter } from './db-content-api/index.js';
|
|
2
|
+
export type { CloudflareImageSizeOptions } from './imageSizeOptions.js';
|
|
2
3
|
export { figmaContentSystem } from './plugin/adapter.js';
|
|
3
4
|
export type { FigmaContentSystemOptions } from './plugin/adapter.js';
|
|
4
5
|
export { buildFigmaConfig } from './plugin/build-config.js';
|
package/dist/oauth/defaults.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CollectionConfig, EmailField, TextField } from 'payload';
|
|
2
2
|
import type { VerifyFunction } from './types.js';
|
|
3
3
|
export declare const DEFAULT_USER_INFO_COOKIE_NAME = "figma-user-info";
|
|
4
|
+
export declare const FIGMA_OAUTH_CALLBACK_PATH = "/users/sso/login";
|
|
4
5
|
export declare const defaultScope: string[];
|
|
5
6
|
export declare const defaultUsernameField: TextField;
|
|
6
7
|
export declare const defaultVerify: ({ collection, strategyName, userInfoCookieName, usernameField, }: {
|
package/dist/oauth/defaults.js
CHANGED
|
@@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
3
3
|
import { hasUserTokenPropsChanged } from './utilities/hasUserTokenPropsChanged.js';
|
|
4
4
|
import { isDuplicateKeyError } from './utilities/isDuplicateKeyError.js';
|
|
5
5
|
export const DEFAULT_USER_INFO_COOKIE_NAME = 'figma-user-info';
|
|
6
|
+
export const FIGMA_OAUTH_CALLBACK_PATH = '/users/sso/login';
|
|
6
7
|
function getFigmaUserInfo(headers, cookieName) {
|
|
7
8
|
try {
|
|
8
9
|
const cookies = parseCookies(headers);
|
|
@@ -6,7 +6,8 @@ import { createDebugLogger } from '../utilities/createDebugLogger.js';
|
|
|
6
6
|
import { establishSession } from '../utilities/establishSession.js';
|
|
7
7
|
import { exchangeCodeForAccessToken } from '../utilities/exchangeCodeForAccessToken.js';
|
|
8
8
|
import { extractOrigin } from '../utilities/extractOrigin.js';
|
|
9
|
-
import {
|
|
9
|
+
import { getAdminCollectionSlug } from '../utilities/getAdminCollectionSlug.js';
|
|
10
|
+
import { buildCsrfCookieClearHeader, getOAuthCallbackPath, OAUTH_STATE_CSRF_COOKIE_NAME } from '../utilities/getAuthorizeURL.js';
|
|
10
11
|
import { isAbsoluteURL } from '../utilities/isAbsoluteURL.js';
|
|
11
12
|
export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug, pluginOptions, strategy })=>({
|
|
12
13
|
handler: async (req)=>{
|
|
@@ -151,7 +152,11 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
|
|
|
151
152
|
}
|
|
152
153
|
const redirectUri = formatAdminURL({
|
|
153
154
|
apiRoute: config.routes?.api || '/api',
|
|
154
|
-
path:
|
|
155
|
+
path: getOAuthCallbackPath({
|
|
156
|
+
adminCollectionSlug: getAdminCollectionSlug(config),
|
|
157
|
+
collection,
|
|
158
|
+
endpointSlug
|
|
159
|
+
}),
|
|
155
160
|
serverURL: redirectServerURL
|
|
156
161
|
});
|
|
157
162
|
debugLogger.info({
|
package/dist/oauth/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fieldAffectsData } from 'payload/shared';
|
|
2
|
-
import { defaultVerify } from './defaults.js';
|
|
2
|
+
import { defaultVerify, FIGMA_OAUTH_CALLBACK_PATH } from './defaults.js';
|
|
3
3
|
import { getLoginEndpoint } from './endpoints/getLoginEndpoint.js';
|
|
4
4
|
import { getLogoutEndpoint } from './endpoints/getLogoutEndpoint.js';
|
|
5
5
|
import { getMetaEndpoint } from './endpoints/getMetaEndpoint.js';
|
|
@@ -50,6 +50,7 @@ export const oAuth2Plugin = (pluginOptions)=>(config)=>{
|
|
|
50
50
|
path: '@payloadcms/figma/client#DefaultLoginButton'
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
+
let callbackEndpoint;
|
|
53
54
|
return {
|
|
54
55
|
...config,
|
|
55
56
|
admin: {
|
|
@@ -148,6 +149,20 @@ export const oAuth2Plugin = (pluginOptions)=>(config)=>{
|
|
|
148
149
|
usernameField
|
|
149
150
|
})
|
|
150
151
|
});
|
|
152
|
+
const loginEndpoint = getLoginEndpoint({
|
|
153
|
+
collection: existingCollection,
|
|
154
|
+
collectionOptions,
|
|
155
|
+
endpointSlug,
|
|
156
|
+
pluginOptions,
|
|
157
|
+
strategy
|
|
158
|
+
});
|
|
159
|
+
const collectionCallbackPath = `/${existingCollection.slug}/${endpointSlug}/login`;
|
|
160
|
+
if (existingCollection.slug === adminCollectionSlug && FIGMA_OAUTH_CALLBACK_PATH !== collectionCallbackPath) {
|
|
161
|
+
callbackEndpoint = {
|
|
162
|
+
...loginEndpoint,
|
|
163
|
+
path: FIGMA_OAUTH_CALLBACK_PATH
|
|
164
|
+
};
|
|
165
|
+
}
|
|
151
166
|
const authStrategy = {
|
|
152
167
|
name: `${existingCollection.slug}-${strategyName}`,
|
|
153
168
|
// Bind 'this' to ensure the authenticate function has the correct context
|
|
@@ -180,13 +195,7 @@ export const oAuth2Plugin = (pluginOptions)=>(config)=>{
|
|
|
180
195
|
},
|
|
181
196
|
endpoints: [
|
|
182
197
|
...existingCollection.endpoints || [],
|
|
183
|
-
|
|
184
|
-
collection: existingCollection,
|
|
185
|
-
collectionOptions,
|
|
186
|
-
endpointSlug,
|
|
187
|
-
pluginOptions,
|
|
188
|
-
strategy
|
|
189
|
-
}),
|
|
198
|
+
loginEndpoint,
|
|
190
199
|
getMetaEndpoint({
|
|
191
200
|
collection: existingCollection,
|
|
192
201
|
collectionOptions,
|
|
@@ -237,6 +246,12 @@ export const oAuth2Plugin = (pluginOptions)=>(config)=>{
|
|
|
237
246
|
]
|
|
238
247
|
}
|
|
239
248
|
};
|
|
240
|
-
})
|
|
249
|
+
}),
|
|
250
|
+
endpoints: [
|
|
251
|
+
...config.endpoints ?? [],
|
|
252
|
+
...callbackEndpoint ? [
|
|
253
|
+
callbackEndpoint
|
|
254
|
+
] : []
|
|
255
|
+
]
|
|
241
256
|
};
|
|
242
257
|
};
|