/* eslint-disable no-param-reassign */ import type { Field } from 'payload/types' import { fieldAffectsData } from 'payload/types' import toSnakeCase from 'to-snake-case' import type { PostgresAdapter } from '../../types' import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types' import { isArrayOfRows } from '../../utilities/isArrayOfRows' import { transformArray } from './array' import { transformBlocks } from './blocks' import { transformNumbers } from './numbers' import { transformRelationship } from './relationships' import { transformSelects } from './selects' import { transformTexts } from './texts' type Args = { adapter: PostgresAdapter arrays: { [tableName: string]: ArrayRowToInsert[] } /** * This is the name of the base table */ baseTableName: string blocks: { [blockType: string]: BlockRowToInsert[] } blocksToDelete: Set /** * A snake-case field prefix, representing prior fields * Ex: my_group_my_named_tab_ */ columnPrefix: string data: Record existingLocales?: Record[] /** * A prefix that will retain camel-case formatting, representing prior fields * Ex: myGroup_myNamedTab_ */ fieldPrefix: string fields: Field[] forcedLocale?: string locales: { [locale: string]: Record } texts: Record[] numbers: Record[] /** * This is the name of the parent table */ parentTableName: string path: string relationships: Record[] relationshipsToDelete: RelationshipToDelete[] row: Record selects: { [tableName: string]: Record[] } } export const traverseFields = ({ adapter, arrays, baseTableName, blocks, blocksToDelete, columnPrefix, data, existingLocales, fieldPrefix, fields, forcedLocale, locales, texts, numbers, parentTableName, path, relationships, relationshipsToDelete, row, selects, }: Args) => { fields.forEach((field) => { let columnName = '' let fieldName = '' let fieldData: unknown if (fieldAffectsData(field)) { columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}` fieldName = `${fieldPrefix || ''}${field.name}` fieldData = data[field.name] } if (field.type === 'array') { const arrayTableName = `${parentTableName}_${columnName}` if (!arrays[arrayTableName]) arrays[arrayTableName] = [] if (field.localized) { if (typeof data[field.name] === 'object' && data[field.name] !== null) { Object.entries(data[field.name]).forEach(([localeKey, localeData]) => { if (Array.isArray(localeData)) { const newRows = transformArray({ adapter, arrayTableName, baseTableName, blocks, blocksToDelete, data: localeData, field, locale: localeKey, texts, numbers, path, relationships, relationshipsToDelete, selects, }) arrays[arrayTableName] = arrays[arrayTableName].concat(newRows) } }) } } else { const newRows = transformArray({ adapter, arrayTableName, baseTableName, blocks, blocksToDelete, data: data[field.name], field, texts, numbers, path, relationships, relationshipsToDelete, selects, }) arrays[arrayTableName] = arrays[arrayTableName].concat(newRows) } return } if (field.type === 'blocks') { field.blocks.forEach(({ slug }) => { blocksToDelete.add(toSnakeCase(slug)) }) if (field.localized) { if (typeof data[field.name] === 'object' && data[field.name] !== null) { Object.entries(data[field.name]).forEach(([localeKey, localeData]) => { if (Array.isArray(localeData)) { transformBlocks({ adapter, baseTableName, blocks, blocksToDelete, data: localeData, field, locale: localeKey, texts, numbers, path, relationships, relationshipsToDelete, selects, }) } }) } } else if (isArrayOfRows(fieldData)) { transformBlocks({ adapter, baseTableName, blocks, blocksToDelete, data: fieldData, field, texts, numbers, path, relationships, relationshipsToDelete, selects, }) } return } if (field.type === 'group') { if (typeof data[field.name] === 'object' && data[field.name] !== null) { if (field.localized) { Object.entries(data[field.name]).forEach(([localeKey, localeData]) => { traverseFields({ adapter, arrays, baseTableName, blocks, blocksToDelete, columnPrefix: `${columnName}_`, data: localeData as Record, existingLocales, fieldPrefix: `${fieldName}_`, fields: field.fields, forcedLocale: localeKey, locales, texts, numbers, parentTableName, path: `${path || ''}${field.name}.`, relationships, relationshipsToDelete, row, selects, }) }) } else { traverseFields({ adapter, arrays, baseTableName, blocks, blocksToDelete, columnPrefix: `${columnName}_`, data: data[field.name] as Record, existingLocales, fieldPrefix: `${fieldName}_`, fields: field.fields, locales, texts, numbers, parentTableName, path: `${path || ''}${field.name}.`, relationships, relationshipsToDelete, row, selects, }) } } return } if (field.type === 'tabs') { field.tabs.forEach((tab) => { if ('name' in tab) { if (typeof data[tab.name] === 'object' && data[tab.name] !== null) { if (tab.localized) { Object.entries(data[tab.name]).forEach(([localeKey, localeData]) => { traverseFields({ adapter, arrays, baseTableName, blocks, blocksToDelete, columnPrefix: `${columnPrefix || ''}${toSnakeCase(tab.name)}_`, data: localeData as Record, existingLocales, fieldPrefix: `${fieldPrefix || ''}${tab.name}_`, fields: tab.fields, forcedLocale: localeKey, locales, texts, numbers, parentTableName, path: `${path || ''}${tab.name}.`, relationships, relationshipsToDelete, row, selects, }) }) } else { traverseFields({ adapter, arrays, baseTableName, blocks, blocksToDelete, columnPrefix: `${columnPrefix || ''}${toSnakeCase(tab.name)}_`, data: data[tab.name] as Record, existingLocales, fieldPrefix: `${fieldPrefix || ''}${tab.name}_`, fields: tab.fields, locales, texts, numbers, parentTableName, path: `${path || ''}${tab.name}.`, relationships, relationshipsToDelete, row, selects, }) } } } else { traverseFields({ adapter, arrays, baseTableName, blocks, blocksToDelete, columnPrefix, data, existingLocales, fieldPrefix, fields: tab.fields, locales, texts, numbers, parentTableName, path, relationships, relationshipsToDelete, row, selects, }) } }) } if (field.type === 'row' || field.type === 'collapsible') { traverseFields({ adapter, arrays, baseTableName, blocks, blocksToDelete, columnPrefix, data, existingLocales, fieldPrefix, fields: field.fields, locales, texts, numbers, parentTableName, path, relationships, relationshipsToDelete, row, selects, }) } if (field.type === 'relationship' || field.type === 'upload') { const relationshipPath = `${path || ''}${field.name}` if (field.localized) { if (typeof fieldData === 'object') { Object.entries(fieldData).forEach(([localeKey, localeData]) => { if (localeData === null) { relationshipsToDelete.push({ locale: localeKey, path: relationshipPath, }) return } transformRelationship({ baseRow: { locale: localeKey, path: relationshipPath, }, data: localeData, field, relationships, }) }) } } else { if (fieldData === null || (Array.isArray(fieldData) && fieldData.length === 0)) { relationshipsToDelete.push({ path: relationshipPath }) return } transformRelationship({ baseRow: { path: relationshipPath, }, data: fieldData, field, relationships, }) } return } if (field.type === 'text' && field.hasMany) { const textPath = `${path || ''}${field.name}` if (field.localized) { if (typeof fieldData === 'object') { Object.entries(fieldData).forEach(([localeKey, localeData]) => { if (Array.isArray(localeData)) { transformTexts({ baseRow: { locale: localeKey, path: textPath, }, data: localeData, texts, }) } }) } } else if (Array.isArray(fieldData)) { transformTexts({ baseRow: { path: textPath, }, data: fieldData, texts, }) } return } if (field.type === 'number' && field.hasMany) { const numberPath = `${path || ''}${field.name}` if (field.localized) { if (typeof fieldData === 'object') { Object.entries(fieldData).forEach(([localeKey, localeData]) => { if (Array.isArray(localeData)) { transformNumbers({ baseRow: { locale: localeKey, path: numberPath, }, data: localeData, numbers, }) } }) } } else if (Array.isArray(fieldData)) { transformNumbers({ baseRow: { path: numberPath, }, data: fieldData, numbers, }) } return } if (field.type === 'select' && field.hasMany) { const selectTableName = `${parentTableName}_${columnName}` if (!selects[selectTableName]) selects[selectTableName] = [] if (field.localized) { if (typeof data[field.name] === 'object' && data[field.name] !== null) { Object.entries(data[field.name]).forEach(([localeKey, localeData]) => { if (Array.isArray(localeData)) { const newRows = transformSelects({ id: data._uuid || data.id, data: localeData, locale: localeKey, }) selects[selectTableName] = selects[selectTableName].concat(newRows) } }) } } else if (Array.isArray(data[field.name])) { const newRows = transformSelects({ id: data._uuid || data.id, data: data[field.name], }) selects[selectTableName] = selects[selectTableName].concat(newRows) } return } if (fieldAffectsData(field)) { const valuesToTransform: { localeKey?: string; ref: unknown; value: unknown }[] = [] if (field.localized) { if (typeof fieldData === 'object' && fieldData !== null) { Object.entries(fieldData).forEach(([localeKey, localeData]) => { if (!locales[localeKey]) locales[localeKey] = {} valuesToTransform.push({ localeKey, ref: locales, value: localeData, }) }) } } else { let ref = row if (forcedLocale) { if (!locales[forcedLocale]) locales[forcedLocale] = {} ref = locales[forcedLocale] } valuesToTransform.push({ ref, value: fieldData }) } valuesToTransform.forEach(({ localeKey, ref, value }) => { if (typeof value !== 'undefined') { let formattedValue = value if (field.type === 'date' && field.name === 'updatedAt') { formattedValue = new Date().toISOString() } if (localeKey) { ref[localeKey][fieldName] = formattedValue } else { ref[fieldName] = formattedValue } } }) } }) }