@ainsleydev/payload-helper 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/eslint.config.mjs +4 -0
- package/package.json +40 -0
- package/src/collections/Media.ts +149 -0
- package/src/collections/Redirects.ts +58 -0
- package/src/common/SEO.ts +45 -0
- package/src/endpoints/slug.ts +30 -0
- package/src/gen/schema.ts +584 -0
- package/src/globals/Navigation.ts +165 -0
- package/src/globals/Settings.ts +356 -0
- package/src/globals/countries.ts +196 -0
- package/src/globals/locales.ts +2668 -0
- package/src/scripts/.gitkeep +0 -0
- package/src/seed/.gitkeep +0 -0
- package/src/util/env.ts +96 -0
- package/src/util/validation.ts +24 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import type { JSONSchema4, JSONSchema4TypeName } from 'json-schema';
|
|
2
|
+
|
|
3
|
+
import { singular } from 'pluralize';
|
|
4
|
+
|
|
5
|
+
import type { Payload } from 'payload';
|
|
6
|
+
import {
|
|
7
|
+
Field,
|
|
8
|
+
FieldAffectingData,
|
|
9
|
+
Option,
|
|
10
|
+
SanitizedGlobalConfig,
|
|
11
|
+
SanitizedCollectionConfig,
|
|
12
|
+
} from 'payload/types';
|
|
13
|
+
import { SanitizedConfig } from 'payload/config';
|
|
14
|
+
|
|
15
|
+
import { fieldAffectsData, tabHasName } from 'payload/dist/fields/config/types';
|
|
16
|
+
import { deepCopyObject } from 'payload/dist/utilities/deepCopyObject';
|
|
17
|
+
import { toWords } from 'payload/dist/utilities/formatLabels';
|
|
18
|
+
import { getCollectionIDFieldTypes } from 'payload/dist/utilities/getCollectionIDFieldTypes';
|
|
19
|
+
|
|
20
|
+
const fieldIsRequired = (field: Field) => {
|
|
21
|
+
const isConditional = Boolean(field?.admin && field?.admin?.condition);
|
|
22
|
+
if (isConditional) return false;
|
|
23
|
+
|
|
24
|
+
const isMarkedRequired = 'required' in field && field.required === true;
|
|
25
|
+
if (fieldAffectsData(field) && isMarkedRequired) return true;
|
|
26
|
+
|
|
27
|
+
// if any subfields are required, this field is required
|
|
28
|
+
if ('fields' in field && field.type !== 'array') {
|
|
29
|
+
return field.fields.some((subField) => fieldIsRequired(subField));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// if any tab subfields have required fields, this field is required
|
|
33
|
+
if (field.type === 'tabs') {
|
|
34
|
+
return field.tabs.some((tab) => {
|
|
35
|
+
if ('name' in tab) {
|
|
36
|
+
return tab.fields.some((subField) => fieldIsRequired(subField));
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return false;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function buildOptionEnums(options: Option[]): string[] {
|
|
46
|
+
return options.map((option) => {
|
|
47
|
+
if (typeof option === 'object' && 'value' in option) {
|
|
48
|
+
return option.value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return option;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function generateEntitySchemas(
|
|
56
|
+
entities: (SanitizedCollectionConfig | SanitizedGlobalConfig)[],
|
|
57
|
+
): JSONSchema4 {
|
|
58
|
+
const properties = [...entities].reduce((acc, { slug }) => {
|
|
59
|
+
acc[slug] = {
|
|
60
|
+
$ref: `#/definitions/${slug}`,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return acc;
|
|
64
|
+
}, {});
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
type: 'object',
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
properties,
|
|
70
|
+
required: Object.keys(properties),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns a JSON Schema Type with 'null' added if the field is not required.
|
|
76
|
+
*/
|
|
77
|
+
export function withNullableJSONSchemaType(
|
|
78
|
+
fieldType: JSONSchema4TypeName,
|
|
79
|
+
isRequired: boolean,
|
|
80
|
+
): JSONSchema4TypeName | JSONSchema4TypeName[] {
|
|
81
|
+
const fieldTypes = [fieldType];
|
|
82
|
+
if (isRequired) return fieldType;
|
|
83
|
+
fieldTypes.push('null');
|
|
84
|
+
return fieldTypes;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function fieldsToJSONSchema(
|
|
88
|
+
/**
|
|
89
|
+
* Used for relationship fields, to determine whether to use a string or number type for the ID.
|
|
90
|
+
* While there is a default ID field type set by the db adapter, they can differ on a collection-level
|
|
91
|
+
* if they have custom ID fields.
|
|
92
|
+
*/
|
|
93
|
+
collectionIDFieldTypes: { [key: string]: 'number' | 'string' },
|
|
94
|
+
fields: Field[],
|
|
95
|
+
/**
|
|
96
|
+
* Allows you to define new top-level interfaces that can be re-used in the output schema.
|
|
97
|
+
*/
|
|
98
|
+
interfaceNameDefinitions: Map<string, JSONSchema4>,
|
|
99
|
+
payload?: Payload,
|
|
100
|
+
config?: SanitizedConfig,
|
|
101
|
+
): {
|
|
102
|
+
properties: {
|
|
103
|
+
[k: string]: JSONSchema4;
|
|
104
|
+
};
|
|
105
|
+
required: string[];
|
|
106
|
+
} {
|
|
107
|
+
const requiredFieldNames = new Set<string>();
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
properties: Object.fromEntries(
|
|
111
|
+
fields.reduce((fieldSchemas, field) => {
|
|
112
|
+
const isRequired = fieldAffectsData(field) && fieldIsRequired(field);
|
|
113
|
+
if (isRequired) requiredFieldNames.add(field.name);
|
|
114
|
+
|
|
115
|
+
let fieldSchema: JSONSchema4;
|
|
116
|
+
switch (field.type) {
|
|
117
|
+
case 'text':
|
|
118
|
+
if (field.hasMany === true) {
|
|
119
|
+
fieldSchema = {
|
|
120
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
121
|
+
items: { type: 'string' },
|
|
122
|
+
};
|
|
123
|
+
} else {
|
|
124
|
+
fieldSchema = {
|
|
125
|
+
type: withNullableJSONSchemaType('string', isRequired),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
case 'textarea':
|
|
130
|
+
case 'code':
|
|
131
|
+
case 'email':
|
|
132
|
+
case 'date': {
|
|
133
|
+
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) };
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
case 'number': {
|
|
138
|
+
if (field.hasMany === true) {
|
|
139
|
+
fieldSchema = {
|
|
140
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
141
|
+
items: { type: 'number' },
|
|
142
|
+
};
|
|
143
|
+
} else {
|
|
144
|
+
fieldSchema = {
|
|
145
|
+
type: withNullableJSONSchemaType('number', isRequired),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
case 'checkbox': {
|
|
152
|
+
fieldSchema = { type: withNullableJSONSchemaType('boolean', isRequired) };
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'json': {
|
|
157
|
+
fieldSchema = {
|
|
158
|
+
type: ['object', 'array', 'string', 'number', 'boolean', 'null'],
|
|
159
|
+
};
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 'richText': {
|
|
164
|
+
if (field.editor.outputSchema) {
|
|
165
|
+
fieldSchema = field.editor.outputSchema({
|
|
166
|
+
collectionIDFieldTypes,
|
|
167
|
+
config: config || payload?.config,
|
|
168
|
+
field,
|
|
169
|
+
interfaceNameDefinitions,
|
|
170
|
+
isRequired,
|
|
171
|
+
payload,
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
// Maintain backwards compatibility with existing rich text editors
|
|
175
|
+
fieldSchema = {
|
|
176
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
177
|
+
items: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
case 'radio': {
|
|
187
|
+
fieldSchema = {
|
|
188
|
+
type: withNullableJSONSchemaType('string', isRequired),
|
|
189
|
+
enum: buildOptionEnums(field.options),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case 'select': {
|
|
196
|
+
const optionEnums = buildOptionEnums(field.options);
|
|
197
|
+
|
|
198
|
+
if (field.hasMany) {
|
|
199
|
+
fieldSchema = {
|
|
200
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
201
|
+
items: {
|
|
202
|
+
type: 'string',
|
|
203
|
+
enum: optionEnums,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
} else {
|
|
207
|
+
fieldSchema = {
|
|
208
|
+
type: withNullableJSONSchemaType('string', isRequired),
|
|
209
|
+
enum: optionEnums,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
case 'point': {
|
|
217
|
+
fieldSchema = {
|
|
218
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
219
|
+
items: [
|
|
220
|
+
{
|
|
221
|
+
type: 'number',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
type: 'number',
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
maxItems: 2,
|
|
228
|
+
minItems: 2,
|
|
229
|
+
};
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case 'relationship': {
|
|
234
|
+
if (Array.isArray(field.relationTo)) {
|
|
235
|
+
if (field.hasMany) {
|
|
236
|
+
fieldSchema = {
|
|
237
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
238
|
+
items: {
|
|
239
|
+
oneOf: field.relationTo.map((relation) => {
|
|
240
|
+
return {
|
|
241
|
+
type: 'object',
|
|
242
|
+
additionalProperties: false,
|
|
243
|
+
properties: {
|
|
244
|
+
relationTo: {
|
|
245
|
+
const: relation,
|
|
246
|
+
},
|
|
247
|
+
value: {
|
|
248
|
+
$ref: `#/definitions/${relation}`,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
required: ['value', 'relationTo'],
|
|
252
|
+
};
|
|
253
|
+
}),
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
} else {
|
|
257
|
+
fieldSchema = {
|
|
258
|
+
oneOf: field.relationTo.map((relation) => {
|
|
259
|
+
return {
|
|
260
|
+
type: withNullableJSONSchemaType('object', isRequired),
|
|
261
|
+
additionalProperties: false,
|
|
262
|
+
properties: {
|
|
263
|
+
relationTo: {
|
|
264
|
+
const: relation,
|
|
265
|
+
},
|
|
266
|
+
value: {
|
|
267
|
+
$ref: `#/definitions/${relation}`,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
required: ['value', 'relationTo'],
|
|
271
|
+
};
|
|
272
|
+
}),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
} else if (field.hasMany) {
|
|
276
|
+
fieldSchema = {
|
|
277
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
278
|
+
items: {
|
|
279
|
+
$ref: `#/definitions/${field.relationTo}`,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
} else {
|
|
283
|
+
fieldSchema = {
|
|
284
|
+
$ref: `#/definitions/${field.relationTo}`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case 'upload': {
|
|
292
|
+
fieldSchema = {
|
|
293
|
+
$ref: `#/definitions/${field.relationTo}`,
|
|
294
|
+
type: withNullableJSONSchemaType('object', isRequired),
|
|
295
|
+
};
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case 'blocks': {
|
|
300
|
+
fieldSchema = {
|
|
301
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
302
|
+
goJSONSchema: {
|
|
303
|
+
imports: ['github.com/ainsleydev/webkit/pkg/adapters/payload'],
|
|
304
|
+
nillable: false,
|
|
305
|
+
type: 'payload.Blocks',
|
|
306
|
+
},
|
|
307
|
+
items: {
|
|
308
|
+
oneOf: field.blocks.map((block) => {
|
|
309
|
+
const blockFieldSchemas = fieldsToJSONSchema(
|
|
310
|
+
collectionIDFieldTypes,
|
|
311
|
+
block.fields,
|
|
312
|
+
interfaceNameDefinitions,
|
|
313
|
+
payload,
|
|
314
|
+
config,
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const blockSchema: JSONSchema4 = {
|
|
318
|
+
type: 'object',
|
|
319
|
+
additionalProperties: false,
|
|
320
|
+
properties: {
|
|
321
|
+
...blockFieldSchemas.properties,
|
|
322
|
+
blockType: {
|
|
323
|
+
type: 'string',
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
required: ['blockType', ...blockFieldSchemas.required],
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
if (block.interfaceName) {
|
|
330
|
+
interfaceNameDefinitions.set(
|
|
331
|
+
block.interfaceName,
|
|
332
|
+
blockSchema,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
$ref: `#/definitions/${block.interfaceName}`,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return blockSchema;
|
|
341
|
+
}),
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
case 'array': {
|
|
348
|
+
fieldSchema = {
|
|
349
|
+
type: withNullableJSONSchemaType('array', isRequired),
|
|
350
|
+
items: {
|
|
351
|
+
type: 'object',
|
|
352
|
+
additionalProperties: false,
|
|
353
|
+
...fieldsToJSONSchema(
|
|
354
|
+
collectionIDFieldTypes,
|
|
355
|
+
field.fields,
|
|
356
|
+
interfaceNameDefinitions,
|
|
357
|
+
payload,
|
|
358
|
+
config,
|
|
359
|
+
),
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
if (field.interfaceName) {
|
|
364
|
+
interfaceNameDefinitions.set(field.interfaceName, fieldSchema);
|
|
365
|
+
|
|
366
|
+
fieldSchema = {
|
|
367
|
+
$ref: `#/definitions/${field.interfaceName}`,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
case 'row':
|
|
374
|
+
case 'collapsible': {
|
|
375
|
+
const childSchema = fieldsToJSONSchema(
|
|
376
|
+
collectionIDFieldTypes,
|
|
377
|
+
field.fields,
|
|
378
|
+
interfaceNameDefinitions,
|
|
379
|
+
payload,
|
|
380
|
+
config,
|
|
381
|
+
);
|
|
382
|
+
Object.entries(childSchema.properties).forEach(([propName, propSchema]) => {
|
|
383
|
+
fieldSchemas.set(propName, propSchema);
|
|
384
|
+
});
|
|
385
|
+
childSchema.required.forEach((propName) => {
|
|
386
|
+
requiredFieldNames.add(propName);
|
|
387
|
+
});
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
case 'tabs': {
|
|
392
|
+
field.tabs.forEach((tab) => {
|
|
393
|
+
const childSchema = fieldsToJSONSchema(
|
|
394
|
+
collectionIDFieldTypes,
|
|
395
|
+
tab.fields,
|
|
396
|
+
interfaceNameDefinitions,
|
|
397
|
+
payload,
|
|
398
|
+
config,
|
|
399
|
+
);
|
|
400
|
+
if (tabHasName(tab)) {
|
|
401
|
+
// could have interface
|
|
402
|
+
fieldSchemas.set(tab.name, {
|
|
403
|
+
type: 'object',
|
|
404
|
+
additionalProperties: false,
|
|
405
|
+
...childSchema,
|
|
406
|
+
});
|
|
407
|
+
requiredFieldNames.add(tab.name);
|
|
408
|
+
} else {
|
|
409
|
+
Object.entries(childSchema.properties).forEach(
|
|
410
|
+
([propName, propSchema]) => {
|
|
411
|
+
fieldSchemas.set(propName, propSchema);
|
|
412
|
+
},
|
|
413
|
+
);
|
|
414
|
+
childSchema.required.forEach((propName) => {
|
|
415
|
+
requiredFieldNames.add(propName);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
case 'group': {
|
|
423
|
+
// If the group name is Meta (SEO) assign to payload type.
|
|
424
|
+
if (field.name === 'meta') {
|
|
425
|
+
fieldSchema = {
|
|
426
|
+
type: 'object',
|
|
427
|
+
additionalProperties: false,
|
|
428
|
+
goJSONSchema: {
|
|
429
|
+
imports: ['github.com/ainsleydev/webkit/pkg/adapters/payload'],
|
|
430
|
+
nillable: false,
|
|
431
|
+
type: 'payload.SettingsMeta',
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
fieldSchema = {
|
|
438
|
+
type: 'object',
|
|
439
|
+
additionalProperties: false,
|
|
440
|
+
...fieldsToJSONSchema(
|
|
441
|
+
collectionIDFieldTypes,
|
|
442
|
+
field.fields,
|
|
443
|
+
interfaceNameDefinitions,
|
|
444
|
+
payload,
|
|
445
|
+
config,
|
|
446
|
+
),
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
if (field.interfaceName) {
|
|
450
|
+
interfaceNameDefinitions.set(field.interfaceName, fieldSchema);
|
|
451
|
+
|
|
452
|
+
fieldSchema = {
|
|
453
|
+
$ref: `#/definitions/${field.interfaceName}`,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
default: {
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (fieldSchema && fieldAffectsData(field)) {
|
|
466
|
+
fieldSchemas.set(field.name, fieldSchema);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return fieldSchemas;
|
|
470
|
+
}, new Map<string, JSONSchema4>()),
|
|
471
|
+
),
|
|
472
|
+
required: Array.from(requiredFieldNames),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// This function is part of the public API and is exported through payload/utilities
|
|
477
|
+
export function entityToJSONSchema(
|
|
478
|
+
config: SanitizedConfig,
|
|
479
|
+
incomingEntity: SanitizedCollectionConfig | SanitizedGlobalConfig,
|
|
480
|
+
interfaceNameDefinitions: Map<string, JSONSchema4>,
|
|
481
|
+
defaultIDType: 'number' | 'text',
|
|
482
|
+
payload?: Payload,
|
|
483
|
+
): JSONSchema4 {
|
|
484
|
+
const entity: SanitizedCollectionConfig | SanitizedGlobalConfig =
|
|
485
|
+
deepCopyObject(incomingEntity);
|
|
486
|
+
const title = entity.typescript?.interface
|
|
487
|
+
? entity.typescript.interface
|
|
488
|
+
: singular(toWords(entity.slug, true));
|
|
489
|
+
|
|
490
|
+
const idField: FieldAffectingData = {
|
|
491
|
+
name: 'id',
|
|
492
|
+
type: defaultIDType as 'text',
|
|
493
|
+
required: true,
|
|
494
|
+
};
|
|
495
|
+
const customIdField = entity.fields.find(
|
|
496
|
+
(field) => fieldAffectsData(field) && field.name === 'id',
|
|
497
|
+
) as FieldAffectingData;
|
|
498
|
+
|
|
499
|
+
if (customIdField && customIdField.type !== 'group' && customIdField.type !== 'tab') {
|
|
500
|
+
customIdField.required = true;
|
|
501
|
+
} else {
|
|
502
|
+
entity.fields.unshift(idField);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// mark timestamp fields required
|
|
506
|
+
if ('timestamps' in entity && entity.timestamps !== false) {
|
|
507
|
+
entity.fields = entity.fields.map((field) => {
|
|
508
|
+
if (
|
|
509
|
+
fieldAffectsData(field) &&
|
|
510
|
+
(field.name === 'createdAt' || field.name === 'updatedAt')
|
|
511
|
+
) {
|
|
512
|
+
return {
|
|
513
|
+
...field,
|
|
514
|
+
required: true,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
return field;
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if ('auth' in entity && entity.auth && !entity.auth?.disableLocalStrategy) {
|
|
522
|
+
entity.fields.push({
|
|
523
|
+
name: 'password',
|
|
524
|
+
type: 'text',
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Used for relationship fields, to determine whether to use a string or number type for the ID.
|
|
529
|
+
const collectionIDFieldTypes = getCollectionIDFieldTypes({ config, defaultIDType });
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
type: 'object',
|
|
533
|
+
additionalProperties: false,
|
|
534
|
+
title,
|
|
535
|
+
...fieldsToJSONSchema(
|
|
536
|
+
collectionIDFieldTypes,
|
|
537
|
+
entity.fields,
|
|
538
|
+
interfaceNameDefinitions,
|
|
539
|
+
payload,
|
|
540
|
+
config,
|
|
541
|
+
),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* This is used for generating the TypeScript types (payload-types.ts) with the payload generate:types command.
|
|
547
|
+
*/
|
|
548
|
+
export function configToJSONSchema(
|
|
549
|
+
config: SanitizedConfig,
|
|
550
|
+
defaultIDType?: 'number' | 'text',
|
|
551
|
+
payload?: Payload,
|
|
552
|
+
): JSONSchema4 {
|
|
553
|
+
// a mutable Map to store custom top-level `interfaceName` types. Fields with an `interfaceName` property will be moved to the top-level definitions here
|
|
554
|
+
const interfaceNameDefinitions: Map<string, JSONSchema4> = new Map();
|
|
555
|
+
|
|
556
|
+
// Collections and Globals have to be moved to the top-level definitions as well. Reason: The top-level type will be the `Config` type - we don't want all collection and global
|
|
557
|
+
// types to be inlined inside the `Config` type
|
|
558
|
+
const entityDefinitions: { [k: string]: JSONSchema4 } = [
|
|
559
|
+
...config.globals,
|
|
560
|
+
...config.collections,
|
|
561
|
+
].reduce((acc, entity) => {
|
|
562
|
+
acc[entity.slug] = entityToJSONSchema(
|
|
563
|
+
config,
|
|
564
|
+
entity,
|
|
565
|
+
interfaceNameDefinitions,
|
|
566
|
+
defaultIDType,
|
|
567
|
+
payload,
|
|
568
|
+
);
|
|
569
|
+
return acc;
|
|
570
|
+
}, {});
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
additionalProperties: false,
|
|
574
|
+
definitions: { ...entityDefinitions, ...Object.fromEntries(interfaceNameDefinitions) },
|
|
575
|
+
// These properties here will be very simple, as all the complexity is in the definitions. These are just the properties for the top-level `Config` type
|
|
576
|
+
type: 'object',
|
|
577
|
+
properties: {
|
|
578
|
+
collections: generateEntitySchemas(config.collections || []),
|
|
579
|
+
globals: generateEntitySchemas(config.globals || []),
|
|
580
|
+
},
|
|
581
|
+
required: ['collections', 'globals'],
|
|
582
|
+
title: 'Config',
|
|
583
|
+
};
|
|
584
|
+
}
|