From e120d08282ee7a7f6221f925fa3b98ba67bf76b7 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 3 Aug 2023 16:01:57 -0400 Subject: [PATCH] chore: begins insert --- packages/db-postgres/src/create/index.ts | 21 ++ packages/db-postgres/src/create/insertRows.ts | 71 +++++ .../db-postgres/src/create/traverseFields.ts | 198 ++++++++++++ packages/db-postgres/src/index.ts | 4 +- packages/db-postgres/src/schema/build.ts | 22 +- .../db-postgres/src/schema/traverseFields.ts | 41 ++- packages/db-postgres/src/transform/index.ts | 49 +++ .../db-postgres/src/transform/mergeLocales.ts | 68 +++++ .../src/transform/traverseFields.ts | 286 ++++++++++++++++++ .../src/utilities/createBlocksMap.ts | 42 +++ .../src/utilities/createRelationshipMap.ts | 22 ++ test/postgres/config.ts | 106 +++++++ 12 files changed, 918 insertions(+), 12 deletions(-) create mode 100644 packages/db-postgres/src/create/index.ts create mode 100644 packages/db-postgres/src/create/insertRows.ts create mode 100644 packages/db-postgres/src/create/traverseFields.ts create mode 100644 packages/db-postgres/src/transform/index.ts create mode 100644 packages/db-postgres/src/transform/mergeLocales.ts create mode 100644 packages/db-postgres/src/transform/traverseFields.ts create mode 100644 packages/db-postgres/src/utilities/createBlocksMap.ts create mode 100644 packages/db-postgres/src/utilities/createRelationshipMap.ts diff --git a/packages/db-postgres/src/create/index.ts b/packages/db-postgres/src/create/index.ts new file mode 100644 index 0000000000..189ffba223 --- /dev/null +++ b/packages/db-postgres/src/create/index.ts @@ -0,0 +1,21 @@ +import { Create } from 'payload/dist/database/types'; +import toSnakeCase from 'to-snake-case'; +import { insertRows } from './insertRows'; + +export const create: Create = async function create({ + collection: collectionSlug, + data, + // fallbackLocale, + locale, +}) { + const collection = this.payload.collections[collectionSlug].config; + + return insertRows({ + adapter: this, + data, + fallbackLocale: false, + fields: collection.fields, + locale, + tableName: toSnakeCase(collectionSlug), + }); +}; diff --git a/packages/db-postgres/src/create/insertRows.ts b/packages/db-postgres/src/create/insertRows.ts new file mode 100644 index 0000000000..b78fe1978b --- /dev/null +++ b/packages/db-postgres/src/create/insertRows.ts @@ -0,0 +1,71 @@ +import { Field } from 'payload/types'; +import toSnakeCase from 'to-snake-case'; +import { fieldAffectsData } from 'payload/dist/fields/config/types'; +import { PostgresAdapter } from '../types'; +import { traverseFields } from './traverseFields'; +import { transform } from '../transform'; + +type Args = { + adapter: PostgresAdapter + data: Record + fallbackLocale?: string | false + fields: Field[] + locale: string + tableName: string +} + +export const insertRows = async ({ + adapter, + data, + fallbackLocale, + fields, + locale, + tableName, +}: Args): Promise> => { + const row: Record = {}; + const localeRow: Record = {}; + const relationshipRows: Record[] = []; + + await traverseFields({ + adapter, + data, + fields, + locale, + localeRow, + relationshipRows, + row, + tableName, + }); + + const [insertedRow] = await adapter.db.insert(adapter.tables[tableName]) + .values(row).returning(); + + const result: Record = { ...insertedRow }; + + if (Object.keys(localeRow).length > 0) { + localeRow._parentID = insertedRow.id; + localeRow._locale = locale; + const [insertedLocaleRow] = await adapter.db.insert(adapter.tables[`${tableName}_locales`]) + .values(localeRow).returning(); + + result._locales = insertedLocaleRow; + } + + if (relationshipRows.length > 0) { + const insertedRelationshipRows = await adapter.db.insert(adapter.tables[`${tableName}_relationships`]) + .values(relationshipRows.map((relationRow) => ({ + ...relationRow, + parent: insertedRow.id, + }))).returning(); + + result._relationships = insertedRelationshipRows; + } + + return transform({ + config: adapter.payload.config, + data: result, + fallbackLocale, + fields, + locale, + }); +}; diff --git a/packages/db-postgres/src/create/traverseFields.ts b/packages/db-postgres/src/create/traverseFields.ts new file mode 100644 index 0000000000..91652bdec9 --- /dev/null +++ b/packages/db-postgres/src/create/traverseFields.ts @@ -0,0 +1,198 @@ +import { Field } from 'payload/types'; +import toSnakeCase from 'to-snake-case'; +import { fieldAffectsData } from 'payload/dist/fields/config/types'; +import { PostgresAdapter } from '../types'; + +type Args = { + adapter: PostgresAdapter + columnPrefix?: string + data: Record + fields: Field[] + locale: string + localeRow: Record + relationshipRows: Record[] + row: Record + tableName: string +} + +export const traverseFields = async ({ + adapter, + columnPrefix, + data, + fields, + locale, + localeRow, + relationshipRows, + row, + tableName, +}: Args) => { + let targetRow = row; + + fields.forEach((field) => { + let columnName: string; + + if (fieldAffectsData(field)) { + columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}`; + + if (field.localized) { + targetRow = localeRow; + } + } + + switch (field.type) { + case 'number': { + // TODO: handle hasMany + targetRow[columnName] = data[columnName]; + break; + } + + case 'select': { + break; + } + + case 'array': { + break; + } + + case 'blocks': { + // field.blocks.forEach((block) => { + // const baseColumns: Record = { + // _order: integer('_order').notNull(), + // _path: text('_path').notNull(), + // _parentID: parentIDColumnMap[parentIDColType]('_parent_id').references(() => adapter.tables[tableName].id).notNull(), + // }; + + // if (field.localized && adapter.payload.config.localization) { + // baseColumns._locale = adapter.enums._locales('_locale').notNull(); + // } + + // const blockTableName = `${tableName}_${toSnakeCase(block.slug)}`; + + // if (!adapter.tables[blockTableName]) { + // const { arrayBlockRelations: subArrayBlockRelations } = buildTable({ + // adapter, + // baseColumns, + // fields: block.fields, + // tableName: blockTableName, + // }); + + // const blockTableRelations = relations(adapter.tables[blockTableName], ({ many, one }) => { + // const result: Record> = { + // _parentID: one(adapter.tables[tableName], { + // fields: [adapter.tables[blockTableName]._parentID], + // references: [adapter.tables[tableName].id], + // }), + // }; + + // if (field.localized) { + // result._locales = many(adapter.tables[`${blockTableName}_locales`]); + // } + + // subArrayBlockRelations.forEach((val, key) => { + // result[key] = many(adapter.tables[val]); + // }); + + // return result; + // }); + + // adapter.relations[blockTableName] = blockTableRelations; + // } + + // arrayBlockRelations.set(`_${fieldPrefix || ''}${field.name}`, blockTableName); + // }); + + break; + } + + case 'group': { + // Todo: determine what should happen if groups are set to localized + // const { hasLocalizedField: groupHasLocalizedField } = traverseFields({ + // adapter, + // arrayBlockRelations, + // buildRelationships, + // columnPrefix: `${columnName}_`, + // columns, + // fieldPrefix: `${fieldPrefix || ''}${field.name}_`, + // fields: field.fields, + // indexes, + // localesColumns, + // localesIndexes, + // tableName, + // relationships, + // }); + + // if (groupHasLocalizedField) hasLocalizedField = true; + + break; + } + + case 'tabs': { + // field.tabs.forEach((tab) => { + // if ('name' in tab) { + // const { hasLocalizedField: tabHasLocalizedField } = traverseFields({ + // adapter, + // arrayBlockRelations, + // buildRelationships, + // columnPrefix: `${columnName}_`, + // columns, + // fieldPrefix: `${fieldPrefix || ''}${tab.name}_`, + // fields: tab.fields, + // indexes, + // localesColumns, + // localesIndexes, + // tableName, + // relationships, + // }); + + // if (tabHasLocalizedField) hasLocalizedField = true; + // } else { + // ({ hasLocalizedField } = traverseFields({ + // adapter, + // arrayBlockRelations, + // buildRelationships, + // columns, + // fields: tab.fields, + // indexes, + // localesColumns, + // localesIndexes, + // tableName, + // relationships, + // })); + // } + // }); + break; + } + + case 'row': + case 'collapsible': { + // ({ hasLocalizedField } = traverseFields({ + // adapter, + // arrayBlockRelations, + // buildRelationships, + // columns, + // fields: field.fields, + // indexes, + // localesColumns, + // localesIndexes, + // tableName, + // relationships, + // })); + break; + } + + case 'relationship': + case 'upload': + // if (Array.isArray(field.relationTo)) { + // field.relationTo.forEach((relation) => relationships.add(relation)); + // } else { + // relationships.add(field.relationTo); + // } + break; + + default: { + targetRow[columnName] = data[columnName]; + break; + } + } + }); +}; diff --git a/packages/db-postgres/src/index.ts b/packages/db-postgres/src/index.ts index f5dc7c5cd0..48e47ba81d 100644 --- a/packages/db-postgres/src/index.ts +++ b/packages/db-postgres/src/index.ts @@ -14,7 +14,7 @@ import { Args, PostgresAdapter, PostgresAdapterResult } from './types'; // import { find } from './find'; // import { findGlobalVersions } from './findGlobalVersions'; // import { findVersions } from './findVersions'; -// import { create } from './create'; +import { create } from './create'; // import { deleteOne } from './deleteOne'; // import { deleteVersions } from './deleteVersions'; // import { findGlobal } from './findGlobal'; @@ -47,7 +47,7 @@ export function postgresAdapter(args: Args): PostgresAdapterResult { // queryDrafts, // findOne, // find, - // create, + create, // updateOne, // deleteOne, // deleteMany, diff --git a/packages/db-postgres/src/schema/build.ts b/packages/db-postgres/src/schema/build.ts index f1158a56ff..9dd34b2580 100644 --- a/packages/db-postgres/src/schema/build.ts +++ b/packages/db-postgres/src/schema/build.ts @@ -47,6 +47,7 @@ export const buildTable = ({ const indexes: Record IndexBuilder> = {}; let hasLocalizedField = false; + let hasLocalizedRelationshipField = false; const localesColumns: Record = {}; const localesIndexes: Record IndexBuilder> = {}; let localesTable: GenericTable; @@ -73,7 +74,7 @@ export const buildTable = ({ columns.id = serial('id').primaryKey(); } - ({ hasLocalizedField } = traverseFields({ + ({ hasLocalizedField, hasLocalizedRelationshipField } = traverseFields({ adapter, arrayBlockRelations, buildRelationships, @@ -129,13 +130,28 @@ export const buildTable = ({ order: integer('order'), }; + if (hasLocalizedRelationshipField) { + relationshipColumns.locale = adapter.enums._locale('locale'); + } + relationships.forEach((relationTo) => { const formattedRelationTo = toSnakeCase(relationTo); - relationshipColumns[`${relationTo}ID`] = integer(`${formattedRelationTo}_id`).references(() => adapter.tables[formattedRelationTo].id); + let colType = 'integer'; + const relatedCollectionCustomID = adapter.payload.collections[relationTo].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id'); + if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'; + if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'; + + relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](`${formattedRelationTo}_id`).references(() => adapter.tables[formattedRelationTo].id); }); const relationshipsTableName = `${formattedTableName}_relationships`; - relationshipsTable = pgTable(relationshipsTableName, relationshipColumns); + + relationshipsTable = pgTable(relationshipsTableName, relationshipColumns, (cols) => { + const result: Record = {}; + if (hasLocalizedRelationshipField) result.localeIdx = index('locale_idx').on(cols.locale); + return result; + }); + adapter.tables[relationshipsTableName] = relationshipsTable; const relationshipsTableRelations = relations(relationshipsTable, ({ one, many }) => { diff --git a/packages/db-postgres/src/schema/traverseFields.ts b/packages/db-postgres/src/schema/traverseFields.ts index 051d5165c9..3601f78904 100644 --- a/packages/db-postgres/src/schema/traverseFields.ts +++ b/packages/db-postgres/src/schema/traverseFields.ts @@ -24,6 +24,11 @@ type Args = { relationships: Set } +type Result = { + hasLocalizedField: boolean + hasLocalizedRelationshipField: boolean +} + export const traverseFields = ({ adapter, arrayBlockRelations, @@ -37,8 +42,9 @@ export const traverseFields = ({ localesIndexes, tableName, relationships, -}: Args): { hasLocalizedField: boolean } => { +}: Args): Result => { let hasLocalizedField = false; + let hasLocalizedRelationshipField = false; let parentIDColType = 'integer'; if (columns.id instanceof PgNumericBuilder) parentIDColType = 'numeric'; @@ -72,11 +78,15 @@ export const traverseFields = ({ case 'email': case 'code': case 'textarea': { + // TODO: handle hasMany + // TODO: handle min / max length targetTable[`${fieldPrefix || ''}${field.name}`] = varchar(columnName); break; } case 'number': { + // TODO: handle hasMany + // TODO: handle min / max targetTable[`${fieldPrefix || ''}${field.name}`] = numeric(columnName); break; } @@ -199,7 +209,10 @@ export const traverseFields = ({ case 'group': { // Todo: determine what should happen if groups are set to localized - const { hasLocalizedField: groupHasLocalizedField } = traverseFields({ + const { + hasLocalizedField: groupHasLocalizedField, + hasLocalizedRelationshipField: groupHasLocalizedRelationshipField, + } = traverseFields({ adapter, arrayBlockRelations, buildRelationships, @@ -215,14 +228,17 @@ export const traverseFields = ({ }); if (groupHasLocalizedField) hasLocalizedField = true; - + if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true; break; } case 'tabs': { field.tabs.forEach((tab) => { if ('name' in tab) { - const { hasLocalizedField: tabHasLocalizedField } = traverseFields({ + const { + hasLocalizedField: tabHasLocalizedField, + hasLocalizedRelationshipField: tabHasLocalizedRelationshipField, + } = traverseFields({ adapter, arrayBlockRelations, buildRelationships, @@ -238,8 +254,12 @@ export const traverseFields = ({ }); if (tabHasLocalizedField) hasLocalizedField = true; + if (tabHasLocalizedRelationshipField) hasLocalizedRelationshipField = true; } else { - ({ hasLocalizedField } = traverseFields({ + ({ + hasLocalizedField, + hasLocalizedRelationshipField, + } = traverseFields({ adapter, arrayBlockRelations, buildRelationships, @@ -258,7 +278,10 @@ export const traverseFields = ({ case 'row': case 'collapsible': { - ({ hasLocalizedField } = traverseFields({ + ({ + hasLocalizedField, + hasLocalizedRelationshipField, + } = traverseFields({ adapter, arrayBlockRelations, buildRelationships, @@ -280,6 +303,10 @@ export const traverseFields = ({ } else { relationships.add(field.relationTo); } + + if (field.localized) { + hasLocalizedRelationshipField = true; + } break; default: @@ -287,5 +314,5 @@ export const traverseFields = ({ } }); - return { hasLocalizedField }; + return { hasLocalizedField, hasLocalizedRelationshipField }; }; diff --git a/packages/db-postgres/src/transform/index.ts b/packages/db-postgres/src/transform/index.ts new file mode 100644 index 0000000000..333adeaa08 --- /dev/null +++ b/packages/db-postgres/src/transform/index.ts @@ -0,0 +1,49 @@ +/* eslint-disable no-param-reassign */ +import { Field } from 'payload/types'; +import { TypeWithID } from 'payload/dist/collections/config/types'; +import { SanitizedConfig } from 'payload/config'; +import { traverseFields } from './traverseFields'; +import { createRelationshipMap } from '../utilities/createRelationshipMap'; +import { mergeLocales } from './mergeLocales'; +import { createBlocksMap } from '../utilities/createBlocksMap'; + +type TransformArgs = { + config: SanitizedConfig + data: Record + fallbackLocale?: string | false + fields: Field[] + locale?: string +} + +// This is the entry point to transform Drizzle output data +// into the shape Payload expects based on field schema +export const transform = ({ + config, + data, + fallbackLocale, + fields, + locale, +}: TransformArgs): T => { + let relationships: Record[]> = {}; + + if ('_relationships' in data) { + relationships = createRelationshipMap(data._relationships); + delete data._relationships; + } + + const blocks = createBlocksMap(data); + + const dataWithLocales = mergeLocales({ data, locale, fallbackLocale }); + + return traverseFields({ + blocks, + config, + data, + fields, + locale, + path: '', + relationships, + siblingData: dataWithLocales, + table: dataWithLocales, + }); +}; diff --git a/packages/db-postgres/src/transform/mergeLocales.ts b/packages/db-postgres/src/transform/mergeLocales.ts new file mode 100644 index 0000000000..fe0c80ac67 --- /dev/null +++ b/packages/db-postgres/src/transform/mergeLocales.ts @@ -0,0 +1,68 @@ +/* eslint-disable no-param-reassign */ +type MergeLocalesArgs = { + data: Record + fallbackLocale?: string | false + locale?: string +} + +// Merge _locales into the parent data +// based on which locale(s) are asked for +export const mergeLocales = ({ + data, + fallbackLocale, + locale, +}: MergeLocalesArgs): Record => { + if (Array.isArray(data._locales)) { + if (locale) { + const matchedLocale = data._locales.find((row) => row._locale === locale); + + if (matchedLocale) { + const merged = { + ...data, + ...matchedLocale, + }; + + delete merged._locales; + delete merged._locale; + return merged; + } + + if (fallbackLocale) { + const matchedFallbackLocale = data._locales.find((row) => row._locale === fallbackLocale); + + if (matchedFallbackLocale) { + const merged = { + ...data, + ...matchedFallbackLocale, + }; + delete merged._locales; + delete merged._locale; + return merged; + } + } + } + + const fieldLocales = data._locales.reduce((res, row) => { + const rowLocale = row._locale; + delete row._locale; + + if (rowLocale) { + Object.entries(row).forEach(([field, val]) => { + if (!res[field]) res[field] = {}; + res[field][rowLocale] = val; + }); + } + + return res; + }, {}); + + delete data._locales; + + return { + ...data, + ...fieldLocales, + }; + } + + return data; +}; diff --git a/packages/db-postgres/src/transform/traverseFields.ts b/packages/db-postgres/src/transform/traverseFields.ts new file mode 100644 index 0000000000..58781114e4 --- /dev/null +++ b/packages/db-postgres/src/transform/traverseFields.ts @@ -0,0 +1,286 @@ +/* eslint-disable no-param-reassign */ +import { fieldAffectsData } from 'payload/dist/fields/config/types'; +import { Field } from 'payload/types'; +import { SanitizedConfig } from 'payload/config'; +import { mergeLocales } from './mergeLocales'; +import { BlocksMap } from '../utilities/createBlocksMap'; +import { transform } from '.'; + +type TraverseFieldsArgs = { + /** + * Pre-formatted blocks map + */ + blocks: BlocksMap + /** + * The full Payload config + */ + config: SanitizedConfig + /** + * The full data, as returned from the Drizzle query + */ + data: Record + /** + * The locale to fall back to, if no locale present + */ + fallbackLocale?: string + /** + * An array of Payload fields to traverse + */ + fields: Field[] + /** + * The locale to retrieve + */ + locale?: string + /** + * The current field path (in dot notation), used to merge in relationships + */ + path: string + /** + * All related documents, as returned by Drizzle, keyed on an object by field path + */ + relationships: Record[]> + /** + * Sibling data of the fields to traverse + */ + siblingData: Record + /** + * Data structure representing the nearest table from db + */ + table: Record +} + +// Traverse fields recursively, transforming data +// for each field type into required Payload shape +export const traverseFields = >({ + blocks, + config, + data, + fallbackLocale, + fields, + locale, + path, + relationships, + siblingData, + table, +}: TraverseFieldsArgs): T => { + const sanitizedPath = path ? `${path}.` : path; + + const formatted = fields.reduce((result, field) => { + if (fieldAffectsData(field)) { + const fieldData = result[field.name]; + + switch (field.type) { + case 'array': + if (Array.isArray(fieldData)) { + result[field.name] = fieldData.map((row, i) => { + const dataWithLocales = mergeLocales({ data: row, locale, fallbackLocale }); + + return traverseFields({ + blocks, + config, + data, + fields: field.fields, + locale, + path: `${sanitizedPath}${field.name}.${i}`, + relationships, + siblingData: dataWithLocales, + table: dataWithLocales, + }); + }); + } + + break; + + case 'blocks': { + const blockFieldPath = `${sanitizedPath}${field.name}`; + + if (Array.isArray(blocks[blockFieldPath])) { + result[field.name] = blocks[blockFieldPath].map((row, i) => { + delete row._order; + const dataWithLocales = mergeLocales({ data: row, locale, fallbackLocale }); + const block = field.blocks.find(({ slug }) => slug === row.blockType); + + if (block) { + return traverseFields({ + blocks, + config, + data, + fields: block.fields, + locale, + path: `${blockFieldPath}.${i}`, + relationships, + siblingData: dataWithLocales, + table: dataWithLocales, + }); + } + + return {}; + }); + } + + break; + } + + case 'group': { + const groupData: Record = { + ...(typeof fieldData === 'object' ? fieldData : {}), + }; + + field.fields.forEach((subField) => { + if (fieldAffectsData(subField)) { + const subFieldKey = `${sanitizedPath.replace(/[.]/g, '_')}${field.name}_${subField.name}`; + if (table[subFieldKey]) { + groupData[subField.name] = table[subFieldKey]; + delete table[subFieldKey]; + } + } + }); + + result[field.name] = traverseFields>({ + blocks, + config, + data, + fields: field.fields, + locale, + path: `${sanitizedPath}${field.name}`, + relationships, + siblingData: groupData, + table, + }); + + break; + } + + case 'relationship': { + const relationPathMatch = relationships[`${sanitizedPath}${field.name}`]; + + if (!field.hasMany) { + const relation = relationPathMatch[0]; + + if (relation) { + // Handle hasOne Poly + if (Array.isArray(field.relationTo)) { + const matchedRelation = Object.entries(relation).find(([, val]) => val !== null); + + if (matchedRelation) { + const relationTo = matchedRelation[0].replace('ID', ''); + + if (typeof matchedRelation[1] === 'object') { + const relatedCollection = config.collections.find(({ slug }) => slug === relationTo); + + if (relatedCollection) { + const value = transform({ + config, + data: matchedRelation[1] as Record, + fallbackLocale, + fields: relatedCollection.fields, + locale, + }); + + result[field.name] = { + relationTo, + value, + }; + } + } else { + result[field.name] = { + relationTo, + value: matchedRelation[1], + }; + } + } + } else { + // Handle hasOne + const relatedData = relation[`${field.relationTo}ID`]; + + if (typeof relatedData === 'object' && relatedData !== null) { + const relatedCollection = config.collections.find(({ slug }) => slug === field.relationTo); + result[field.name] = transform({ + config, + data: relatedData as Record, + fallbackLocale, + fields: relatedCollection.fields, + locale, + }); + } else { + result[field.name] = relatedData; + } + } + } + } else { + const transformedRelations = [ + ...(Array.isArray(fieldData) ? fieldData : []), + ]; + + relationPathMatch.forEach((relation) => { + // Handle hasMany + if (!Array.isArray(field.relationTo)) { + const relatedCollection = config.collections.find(({ slug }) => slug === field.relationTo); + const relatedData = relation[`${field.relationTo}ID`]; + + if (relatedData) { + if (typeof relatedData === 'object' && relatedData !== null) { + transformedRelations.push(transform({ + config, + data: relatedData as Record, + fallbackLocale, + fields: relatedCollection.fields, + locale, + })); + } else { + transformedRelations.push(relatedData); + } + } + } else { + // Handle hasMany Poly + const matchedRelation = Object.entries(relation).find(([key, val]) => val !== null && key !== 'order'); + + if (matchedRelation) { + const relationTo = matchedRelation[0].replace('ID', ''); + + if (typeof matchedRelation[1] === 'object') { + const relatedCollection = config.collections.find(({ slug }) => slug === relationTo); + + if (relatedCollection) { + const value = transform({ + config, + data: matchedRelation[1] as Record, + fallbackLocale, + fields: relatedCollection.fields, + locale, + }); + + transformedRelations.push({ + relationTo, + value, + }); + } + } else { + transformedRelations.push({ + relationTo, + value: matchedRelation[1], + }); + } + } + } + }); + + result[field.name] = transformedRelations; + } + + break; + } + + default: { + break; + } + } + + return result; + } + + return siblingData; + }, siblingData); + + return formatted as T; +}; diff --git a/packages/db-postgres/src/utilities/createBlocksMap.ts b/packages/db-postgres/src/utilities/createBlocksMap.ts new file mode 100644 index 0000000000..e68a4d03f0 --- /dev/null +++ b/packages/db-postgres/src/utilities/createBlocksMap.ts @@ -0,0 +1,42 @@ +/* eslint-disable no-param-reassign */ +export type BlocksMap = { + [path: string]: Record[] +} + +export const createBlocksMap = (data: Record): BlocksMap => { + const blocksMap: BlocksMap = {}; + + Object.entries(data).forEach(([key, rows]) => { + if (key.startsWith('_blocks_') && Array.isArray(rows)) { + const blockType = key.replace('_blocks_', ''); + + rows.forEach((row) => { + if ('_path' in row) { + if (!(row._path in blocksMap)) blocksMap[row._path] = []; + + row.blockType = blockType; + blocksMap[row._path].push(row); + + delete row._locale; + delete row._path; + } + }); + + delete data[key]; + } + }); + + Object.entries(blocksMap).reduce((sortedBlocksMap, [path, blocks]) => { + sortedBlocksMap[path] = blocks.sort((a, b) => { + if (typeof a._order === 'number' && typeof b._order === 'number') { + return a._order - b._order; + } + + return 0; + }); + + return sortedBlocksMap; + }, {}); + + return blocksMap; +}; diff --git a/packages/db-postgres/src/utilities/createRelationshipMap.ts b/packages/db-postgres/src/utilities/createRelationshipMap.ts new file mode 100644 index 0000000000..e2f1cca16a --- /dev/null +++ b/packages/db-postgres/src/utilities/createRelationshipMap.ts @@ -0,0 +1,22 @@ +// Flatten relationships to object with path keys +// for easier retrieval +export const createRelationshipMap = (rawRelationships: unknown): Record[]> => { + let relationships = {}; + + if (Array.isArray(rawRelationships)) { + relationships = rawRelationships.reduce((res, relation) => { + const formattedRelation = { + ...relation, + }; + + delete formattedRelation.path; + + if (!res[relation.path]) res[relation.path] = []; + res[relation.path].push(formattedRelation); + + return res; + }, {}); + } + + return relationships; +}; diff --git a/test/postgres/config.ts b/test/postgres/config.ts index 848d4525c0..7cf820257b 100644 --- a/test/postgres/config.ts +++ b/test/postgres/config.ts @@ -1,5 +1,6 @@ import { CollectionConfig } from '../../src/collections/config/types'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults'; +import { devUser } from '../credentials'; export const Posts: CollectionConfig = { slug: 'posts', @@ -164,6 +165,111 @@ const config = buildConfigWithDefaults({ locales: ['en', 'es'], defaultLocale: 'en', }, + onInit: async (payload) => { + // await payload.create({ + // collection: 'users', + // data: { + // email: devUser.email, + // password: devUser.password, + // }, + // }); + + const page1 = await payload.create({ + collection: 'pages', + data: { + slug: 'first', + }, + }); + + const page2 = await payload.create({ + collection: 'pages', + data: { + slug: 'second', + }, + }); + + const person1 = await payload.create({ + collection: 'people', + data: { + fullName: 'Dan Ribbens', + }, + }); + + await payload.create({ + collection: 'posts', + data: { + title: 'hello', + number: 1337, + myGroup: { + subField: 'hello', + subFieldLocalized: 'hello in english', + subGroup: { + subSubField: 'sub hello', + subSubFieldLocalized: 'sub hello in english', + }, + groupArray: [ + { + groupArrayText: 'hello 1', + }, + { + groupArrayText: 'hello 2', + }, + ], + }, + relationHasOne: page1.id, + relationHasOnePoly: { + relationTo: 'people', + value: person1.id, + }, + relationHasMany: [page1.id, page2.id], + relationHasManyPoly: [ + { + relationTo: 'people', + value: person1.id, + }, + { + relationTo: 'pages', + value: page2.id, + }, + ], + myArray: [ + { + subField: 'hello 1', + mySubArray: [ + { + subSubField: 'row 1 subrow 1', + }, + { + subSubField: 'row 1 subrow 2', + }, + ], + }, + { + subField: 'hello 2', + mySubArray: [ + { + subSubField: 'row 2 subrow 1', + }, + { + subSubField: 'row 2 subrow 2', + }, + ], + }, + ], + myBlocks: [ + { + blockType: 'block1', + nonLocalizedText: 'hello', + localizedText: 'hello in english', + }, + { + blockType: 'block2', + number: 123, + }, + ], + }, + }); + }, }); export default config;