356 lines
8.9 KiB
TypeScript
356 lines
8.9 KiB
TypeScript
/* eslint-disable no-param-reassign */
|
|
import { Field, fieldAffectsData, TabAsField, tabHasName } from '../../config/types';
|
|
import { PayloadRequest } from '../../../express/types';
|
|
import { traverseFields } from './traverseFields';
|
|
import richTextRelationshipPromise from '../../richText/richTextRelationshipPromise';
|
|
import relationshipPopulationPromise from './relationshipPopulationPromise';
|
|
|
|
type Args = {
|
|
currentDepth: number
|
|
depth: number
|
|
doc: Record<string, unknown>
|
|
field: Field | TabAsField
|
|
fieldPromises: Promise<void>[]
|
|
findMany: boolean
|
|
flattenLocales: boolean
|
|
populationPromises: Promise<void>[]
|
|
req: PayloadRequest
|
|
overrideAccess: boolean
|
|
siblingDoc: Record<string, unknown>
|
|
showHiddenFields: boolean
|
|
}
|
|
|
|
// This function is responsible for the following actions, in order:
|
|
// - Remove hidden fields from response
|
|
// - Flatten locales into requested locale
|
|
// - Sanitize outgoing data (point field, etc)
|
|
// - Execute field hooks
|
|
// - Execute read access control
|
|
// - Populate relationships
|
|
|
|
export const promise = async ({
|
|
currentDepth,
|
|
depth,
|
|
doc,
|
|
field,
|
|
fieldPromises,
|
|
findMany,
|
|
flattenLocales,
|
|
overrideAccess,
|
|
populationPromises,
|
|
req,
|
|
siblingDoc,
|
|
showHiddenFields,
|
|
}: Args): Promise<void> => {
|
|
if (fieldAffectsData(field) && field.hidden && typeof siblingDoc[field.name] !== 'undefined' && !showHiddenFields) {
|
|
delete siblingDoc[field.name];
|
|
}
|
|
|
|
const shouldHoistLocalizedValue = flattenLocales
|
|
&& fieldAffectsData(field)
|
|
&& (typeof siblingDoc[field.name] === 'object' && siblingDoc[field.name] !== null)
|
|
&& field.localized
|
|
&& req.locale !== 'all'
|
|
&& req.payload.config.localization;
|
|
|
|
if (shouldHoistLocalizedValue) {
|
|
// replace actual value with localized value before sanitizing
|
|
// { [locale]: fields } -> fields
|
|
const { locale } = req;
|
|
const value = siblingDoc[field.name][locale];
|
|
const fallbackLocale = req.payload.config.localization && req.payload.config.localization?.fallback && req.fallbackLocale;
|
|
|
|
let hoistedValue = value;
|
|
|
|
if (fallbackLocale && fallbackLocale !== locale) {
|
|
const fallbackValue = siblingDoc[field.name][fallbackLocale];
|
|
const isNullOrUndefined = typeof value === 'undefined' || value === null;
|
|
|
|
if (fallbackValue) {
|
|
switch (field.type) {
|
|
case 'text':
|
|
case 'textarea': {
|
|
if (value === '' || isNullOrUndefined) {
|
|
hoistedValue = fallbackValue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
if (isNullOrUndefined) {
|
|
hoistedValue = fallbackValue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
siblingDoc[field.name] = hoistedValue;
|
|
}
|
|
|
|
// Sanitize outgoing field value
|
|
switch (field.type) {
|
|
case 'group': {
|
|
// Fill groups with empty objects so fields with hooks within groups can populate
|
|
// themselves virtually as necessary
|
|
if (typeof siblingDoc[field.name] === 'undefined') {
|
|
siblingDoc[field.name] = {};
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 'tabs': {
|
|
field.tabs.forEach((tab) => {
|
|
if (tabHasName(tab) && (typeof siblingDoc[tab.name] === 'undefined' || siblingDoc[tab.name] === null)) {
|
|
siblingDoc[tab.name] = {};
|
|
}
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'richText': {
|
|
if (((field.admin?.elements?.includes('relationship') || field.admin?.elements?.includes('upload') || field.admin?.elements?.includes('link')) || !field?.admin?.elements)) {
|
|
populationPromises.push(richTextRelationshipPromise({
|
|
currentDepth,
|
|
depth,
|
|
field,
|
|
overrideAccess,
|
|
req,
|
|
siblingDoc,
|
|
showHiddenFields,
|
|
}));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 'point': {
|
|
const pointDoc = siblingDoc[field.name] as Record<string, unknown>;
|
|
if (Array.isArray(pointDoc?.coordinates) && pointDoc.coordinates.length === 2) {
|
|
siblingDoc[field.name] = pointDoc.coordinates;
|
|
} else {
|
|
siblingDoc[field.name] = undefined;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fieldAffectsData(field)) {
|
|
// Execute hooks
|
|
if (field.hooks?.afterRead) {
|
|
await field.hooks.afterRead.reduce(async (priorHook, currentHook) => {
|
|
await priorHook;
|
|
|
|
const shouldRunHookOnAllLocales = field.localized
|
|
&& (req.locale === 'all' || !flattenLocales)
|
|
&& typeof siblingDoc[field.name] === 'object';
|
|
|
|
if (shouldRunHookOnAllLocales) {
|
|
const hookPromises = Object.entries(siblingDoc[field.name]).map(([locale, value]) => (async () => {
|
|
const hookedValue = await currentHook({
|
|
value,
|
|
originalDoc: doc,
|
|
data: doc,
|
|
siblingData: siblingDoc,
|
|
operation: 'read',
|
|
req,
|
|
});
|
|
|
|
if (hookedValue !== undefined) {
|
|
siblingDoc[field.name][locale] = hookedValue;
|
|
}
|
|
})());
|
|
|
|
await Promise.all(hookPromises);
|
|
} else {
|
|
const hookedValue = await currentHook({
|
|
data: doc,
|
|
findMany,
|
|
originalDoc: doc,
|
|
operation: 'read',
|
|
siblingData: siblingDoc,
|
|
req,
|
|
value: siblingDoc[field.name],
|
|
});
|
|
|
|
if (hookedValue !== undefined) {
|
|
siblingDoc[field.name] = hookedValue;
|
|
}
|
|
}
|
|
}, Promise.resolve());
|
|
}
|
|
|
|
// Execute access control
|
|
if (field.access && field.access.read) {
|
|
const result = overrideAccess ? true : await field.access.read({ req, id: doc.id as string | number, siblingData: siblingDoc, data: doc, doc });
|
|
|
|
if (!result) {
|
|
delete siblingDoc[field.name];
|
|
}
|
|
}
|
|
|
|
if (field.type === 'relationship' || field.type === 'upload') {
|
|
populationPromises.push(relationshipPopulationPromise({
|
|
currentDepth,
|
|
depth,
|
|
field,
|
|
overrideAccess,
|
|
req,
|
|
showHiddenFields,
|
|
siblingDoc,
|
|
}));
|
|
}
|
|
}
|
|
|
|
switch (field.type) {
|
|
case 'group': {
|
|
let groupDoc = siblingDoc[field.name] as Record<string, unknown>;
|
|
if (typeof siblingDoc[field.name] !== 'object') groupDoc = {};
|
|
|
|
traverseFields({
|
|
currentDepth,
|
|
depth,
|
|
doc,
|
|
fieldPromises,
|
|
fields: field.fields,
|
|
findMany,
|
|
flattenLocales,
|
|
overrideAccess,
|
|
populationPromises,
|
|
req,
|
|
siblingDoc: groupDoc,
|
|
showHiddenFields,
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'array': {
|
|
const rows = siblingDoc[field.name];
|
|
|
|
if (Array.isArray(rows)) {
|
|
rows.forEach((row) => {
|
|
traverseFields({
|
|
currentDepth,
|
|
depth,
|
|
doc,
|
|
fields: field.fields,
|
|
fieldPromises,
|
|
findMany,
|
|
flattenLocales,
|
|
overrideAccess,
|
|
populationPromises,
|
|
req,
|
|
siblingDoc: row || {},
|
|
showHiddenFields,
|
|
});
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'blocks': {
|
|
const rows = siblingDoc[field.name];
|
|
|
|
if (Array.isArray(rows)) {
|
|
rows.forEach((row) => {
|
|
const block = field.blocks.find((blockType) => blockType.slug === row.blockType);
|
|
|
|
if (block) {
|
|
traverseFields({
|
|
currentDepth,
|
|
depth,
|
|
doc,
|
|
fields: block.fields,
|
|
fieldPromises,
|
|
findMany,
|
|
flattenLocales,
|
|
overrideAccess,
|
|
populationPromises,
|
|
req,
|
|
siblingDoc: row || {},
|
|
showHiddenFields,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 'row':
|
|
case 'collapsible': {
|
|
traverseFields({
|
|
currentDepth,
|
|
depth,
|
|
doc,
|
|
fieldPromises,
|
|
fields: field.fields,
|
|
findMany,
|
|
flattenLocales,
|
|
overrideAccess,
|
|
populationPromises,
|
|
req,
|
|
siblingDoc,
|
|
showHiddenFields,
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'tab': {
|
|
let tabDoc = siblingDoc;
|
|
if (tabHasName(field)) {
|
|
tabDoc = siblingDoc[field.name] as Record<string, unknown>;
|
|
if (typeof siblingDoc[field.name] !== 'object') tabDoc = {};
|
|
}
|
|
|
|
await traverseFields({
|
|
currentDepth,
|
|
depth,
|
|
doc,
|
|
fieldPromises,
|
|
fields: field.fields,
|
|
findMany,
|
|
flattenLocales,
|
|
overrideAccess,
|
|
populationPromises,
|
|
req,
|
|
siblingDoc: tabDoc,
|
|
showHiddenFields,
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'tabs': {
|
|
traverseFields({
|
|
currentDepth,
|
|
depth,
|
|
doc,
|
|
fieldPromises,
|
|
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
|
findMany,
|
|
flattenLocales,
|
|
overrideAccess,
|
|
populationPromises,
|
|
req,
|
|
siblingDoc,
|
|
showHiddenFields,
|
|
});
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
};
|