@r1tsu/payload
Version:
343 lines (342 loc) • 15 kB
JavaScript
/* eslint-disable no-param-reassign */ import { fieldAffectsData, tabHasName, valueIsValueWithRelation } from '../../config/types.js';
import getValueWithDefault from '../../getDefaultValue.js';
import { cloneDataFromOriginalDoc } from '../beforeChange/cloneDataFromOriginalDoc.js';
import { getExistingRowDoc } from '../beforeChange/getExistingRowDoc.js';
import { traverseFields } from './traverseFields.js';
// This function is responsible for the following actions, in order:
// - Sanitize incoming data
// - Execute field hooks
// - Execute field access control
// - Merge original document data into incoming data
// - Compute default values for undefined fields
export const promise = async ({ id, collection, context, data, doc, field, global, operation, overrideAccess, req, siblingData, siblingDoc })=>{
if (fieldAffectsData(field)) {
if (field.name === 'id') {
if (field.type === 'number' && typeof siblingData[field.name] === 'string') {
const value = siblingData[field.name];
siblingData[field.name] = parseFloat(value);
}
if (field.type === 'text' && typeof siblingData[field.name]?.toString === 'function' && typeof siblingData[field.name] !== 'string') {
siblingData[field.name] = siblingData[field.name].toString();
}
}
// Sanitize incoming data
switch(field.type){
case 'number':
{
if (typeof siblingData[field.name] === 'string') {
const value = siblingData[field.name];
const trimmed = value.trim();
siblingData[field.name] = trimmed.length === 0 ? null : parseFloat(trimmed);
}
break;
}
case 'point':
{
if (Array.isArray(siblingData[field.name])) {
siblingData[field.name] = siblingData[field.name].map((coordinate, i)=>{
if (typeof coordinate === 'string') {
const value = siblingData[field.name][i];
const trimmed = value.trim();
return trimmed.length === 0 ? null : parseFloat(trimmed);
}
return coordinate;
});
}
break;
}
case 'checkbox':
{
if (siblingData[field.name] === 'true') siblingData[field.name] = true;
if (siblingData[field.name] === 'false') siblingData[field.name] = false;
if (siblingData[field.name] === '') siblingData[field.name] = false;
break;
}
case 'richText':
{
if (typeof siblingData[field.name] === 'string') {
try {
const richTextJSON = JSON.parse(siblingData[field.name]);
siblingData[field.name] = richTextJSON;
} catch {
// Disregard this data as it is not valid.
// Will be reported to user by field validation
}
}
break;
}
case 'relationship':
case 'upload':
{
if (siblingData[field.name] === '' || siblingData[field.name] === 'none' || siblingData[field.name] === 'null' || siblingData[field.name] === null) {
if (field.type === 'relationship' && field.hasMany === true) {
siblingData[field.name] = [];
} else {
siblingData[field.name] = null;
}
}
const value = siblingData[field.name];
if (Array.isArray(field.relationTo)) {
if (Array.isArray(value)) {
value.forEach((relatedDoc, i)=>{
const relatedCollection = req.payload.config.collections.find((collection)=>collection.slug === relatedDoc.relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField)=>fieldAffectsData(collectionField) && collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {
siblingData[field.name][i] = {
...relatedDoc,
value: parseFloat(relatedDoc.value)
};
}
});
}
if (field.type === 'relationship' && field.hasMany !== true && valueIsValueWithRelation(value)) {
const relatedCollection = req.payload.config.collections.find((collection)=>collection.slug === value.relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField)=>fieldAffectsData(collectionField) && collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {
siblingData[field.name] = {
...value,
value: parseFloat(value.value)
};
}
}
} else {
if (Array.isArray(value)) {
value.forEach((relatedDoc, i)=>{
const relatedCollection = req.payload.config.collections.find((collection)=>collection.slug === field.relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField)=>fieldAffectsData(collectionField) && collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {
siblingData[field.name][i] = parseFloat(relatedDoc);
}
});
}
if (field.type === 'relationship' && field.hasMany !== true && value) {
const relatedCollection = req.payload.config.collections.find((collection)=>collection.slug === field.relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField)=>fieldAffectsData(collectionField) && collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {
siblingData[field.name] = parseFloat(value);
}
}
}
break;
}
case 'array':
case 'blocks':
{
// Handle cases of arrays being intentionally set to 0
if (siblingData[field.name] === '0' || siblingData[field.name] === 0) {
siblingData[field.name] = [];
}
break;
}
default:
{
break;
}
}
// Execute hooks
if (field.hooks?.beforeValidate) {
await field.hooks.beforeValidate.reduce(async (priorHook, currentHook)=>{
await priorHook;
const hookedValue = await currentHook({
collection,
context,
data,
field,
global,
operation,
originalDoc: doc,
overrideAccess,
previousSiblingDoc: siblingDoc,
req,
siblingData,
value: siblingData[field.name]
});
if (hookedValue !== undefined) {
siblingData[field.name] = hookedValue;
}
}, Promise.resolve());
}
// Execute access control
if (field.access && field.access[operation]) {
const result = overrideAccess ? true : await field.access[operation]({
id,
data,
doc,
req,
siblingData
});
if (!result) {
delete siblingData[field.name];
}
}
if (typeof siblingData[field.name] === 'undefined') {
// If no incoming data, but existing document data is found, merge it in
if (typeof siblingDoc[field.name] !== 'undefined') {
siblingData[field.name] = cloneDataFromOriginalDoc(siblingDoc[field.name]);
// Otherwise compute default value
} else if (typeof field.defaultValue !== 'undefined') {
siblingData[field.name] = await getValueWithDefault({
defaultValue: field.defaultValue,
locale: req.locale,
req,
value: siblingData[field.name]
});
}
}
}
// Traverse subfields
switch(field.type){
case 'group':
{
if (typeof siblingData[field.name] !== 'object') siblingData[field.name] = {};
if (typeof siblingDoc[field.name] !== 'object') siblingDoc[field.name] = {};
const groupData = siblingData[field.name];
const groupDoc = siblingDoc[field.name];
await traverseFields({
id,
collection,
context,
data,
doc,
fields: field.fields,
global,
operation,
overrideAccess,
req,
siblingData: groupData,
siblingDoc: groupDoc
});
break;
}
case 'array':
{
const rows = siblingData[field.name];
if (Array.isArray(rows)) {
const promises = [];
rows.forEach((row)=>{
promises.push(traverseFields({
id,
collection,
context,
data,
doc,
fields: field.fields,
global,
operation,
overrideAccess,
req,
siblingData: row,
siblingDoc: getExistingRowDoc(row, siblingDoc[field.name])
}));
});
await Promise.all(promises);
}
break;
}
case 'blocks':
{
const rows = siblingData[field.name];
if (Array.isArray(rows)) {
const promises = [];
rows.forEach((row)=>{
const rowSiblingDoc = getExistingRowDoc(row, siblingDoc[field.name]);
const blockTypeToMatch = row.blockType || rowSiblingDoc.blockType;
const block = field.blocks.find((blockType)=>blockType.slug === blockTypeToMatch);
if (block) {
row.blockType = blockTypeToMatch;
promises.push(traverseFields({
id,
collection,
context,
data,
doc,
fields: block.fields,
global,
operation,
overrideAccess,
req,
siblingData: row,
siblingDoc: rowSiblingDoc
}));
}
});
await Promise.all(promises);
}
break;
}
case 'row':
case 'collapsible':
{
await traverseFields({
id,
collection,
context,
data,
doc,
fields: field.fields,
global,
operation,
overrideAccess,
req,
siblingData,
siblingDoc
});
break;
}
case 'tab':
{
let tabSiblingData;
let tabSiblingDoc;
if (tabHasName(field)) {
if (typeof siblingData[field.name] !== 'object') siblingData[field.name] = {};
if (typeof siblingDoc[field.name] !== 'object') siblingDoc[field.name] = {};
tabSiblingData = siblingData[field.name];
tabSiblingDoc = siblingDoc[field.name];
} else {
tabSiblingData = siblingData;
tabSiblingDoc = siblingDoc;
}
await traverseFields({
id,
collection,
context,
data,
doc,
fields: field.fields,
global,
operation,
overrideAccess,
req,
siblingData: tabSiblingData,
siblingDoc: tabSiblingDoc
});
break;
}
case 'tabs':
{
await traverseFields({
id,
collection,
context,
data,
doc,
fields: field.tabs.map((tab)=>({
...tab,
type: 'tab'
})),
global,
operation,
overrideAccess,
req,
siblingData,
siblingDoc
});
break;
}
default:
{
break;
}
}
};
//# sourceMappingURL=promise.js.map