fix(db-postgres): ensure globals have createdAt, updatedAt and globalType fields (#10938)

Previously, data for globals was inconsistent across database adapters.
In Postgres, globals didn't store correct `createdAt`, `updatedAt`
fields and the `updateGlobal` lacked the `globalType` field. This PR
solves that without introducing schema changes.
This commit is contained in:
Sasha
2025-02-06 23:48:59 +02:00
committed by GitHub
parent 3ad56cd86f
commit 57143b37d0
7 changed files with 135 additions and 6 deletions

View File

@@ -16,7 +16,9 @@ export async function createGlobal<T extends Record<string, unknown>>(
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug)) const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
const result = await upsertRow<T>({ data.createdAt = new Date().toISOString()
const result = await upsertRow<{ globalType: string } & T>({
adapter: this, adapter: this,
data, data,
db, db,
@@ -26,5 +28,7 @@ export async function createGlobal<T extends Record<string, unknown>>(
tableName, tableName,
}) })
result.globalType = slug
return result return result
} }

View File

@@ -479,8 +479,9 @@ export const traverseFields = ({
} }
valuesToTransform.forEach(({ localeKey, ref, value }) => { valuesToTransform.forEach(({ localeKey, ref, value }) => {
if (typeof value !== 'undefined') {
let formattedValue = value let formattedValue = value
if (typeof value !== 'undefined') {
if (value && field.type === 'point' && adapter.name !== 'sqlite') { if (value && field.type === 'point' && adapter.name !== 'sqlite') {
formattedValue = sql`ST_GeomFromGeoJSON(${JSON.stringify(value)})` formattedValue = sql`ST_GeomFromGeoJSON(${JSON.stringify(value)})`
} }
@@ -490,12 +491,16 @@ export const traverseFields = ({
formattedValue = new Date(value).toISOString() formattedValue = new Date(value).toISOString()
} else if (value instanceof Date) { } else if (value instanceof Date) {
formattedValue = value.toISOString() formattedValue = value.toISOString()
} else if (fieldName === 'updatedAt') { }
// let the db handle this
formattedValue = new Date().toISOString()
} }
} }
if (field.type === 'date' && fieldName === 'updatedAt') {
// let the db handle this
formattedValue = new Date().toISOString()
}
if (typeof formattedValue !== 'undefined') {
if (localeKey) { if (localeKey) {
ref[localeKey][fieldName] = formattedValue ref[localeKey][fieldName] = formattedValue
} else { } else {

View File

@@ -17,7 +17,7 @@ export async function updateGlobal<T extends Record<string, unknown>>(
const existingGlobal = await db.query[tableName].findFirst({}) const existingGlobal = await db.query[tableName].findFirst({})
const result = await upsertRow<T>({ const result = await upsertRow<{ globalType: string } & T>({
...(existingGlobal ? { id: existingGlobal.id, operation: 'update' } : { operation: 'create' }), ...(existingGlobal ? { id: existingGlobal.id, operation: 'update' } : { operation: 'create' }),
adapter: this, adapter: this,
data, data,
@@ -28,5 +28,7 @@ export async function updateGlobal<T extends Record<string, unknown>>(
tableName, tableName,
}) })
result.globalType = slug
return result return result
} }

View File

@@ -244,6 +244,11 @@ export const updateOperation = async <
// ///////////////////////////////////// // /////////////////////////////////////
if (!shouldSaveDraft) { if (!shouldSaveDraft) {
// Ensure global has createdAt
if (!result.createdAt) {
result.createdAt = new Date().toISOString()
}
if (globalExists) { if (globalExists) {
result = await payload.db.updateGlobal({ result = await payload.db.updateGlobal({
slug, slug,

View File

@@ -563,6 +563,24 @@ export default buildConfigWithDefaults({
], ],
versions: true, versions: true,
}, },
{
slug: 'global-2',
fields: [
{
name: 'text',
type: 'text',
},
],
},
{
slug: 'global-3',
fields: [
{
name: 'text',
type: 'text',
},
],
},
], ],
localization: { localization: {
defaultLocale: 'en', defaultLocale: 'en',

View File

@@ -27,6 +27,7 @@ import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { isMongoose } from '../helpers/isMongoose.js' import { isMongoose } from '../helpers/isMongoose.js'
import removeFiles from '../helpers/removeFiles.js' import removeFiles from '../helpers/removeFiles.js'
import { errorOnUnnamedFieldsSlug, postsSlug } from './shared.js' import { errorOnUnnamedFieldsSlug, postsSlug } from './shared.js'
import { Global2 } from './payload-types.js'
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename) const dirname = path.dirname(filename)
@@ -1357,4 +1358,54 @@ describe('database', () => {
const { id: id_2 } = await payload.create({ collection: postsSlug, data: { title: 'ASD' } }) const { id: id_2 } = await payload.create({ collection: postsSlug, data: { title: 'ASD' } })
expect(id_2).not.toBe(id) expect(id_2).not.toBe(id)
}) })
it('payload.db.createGlobal should have globalType, updatedAt, createdAt fields', async () => {
let timestamp = Date.now()
let result = (await payload.db.createGlobal({
slug: 'global-2',
data: { text: 'this is global-2' },
})) as Global2 & { globalType: string }
expect(result.text).toBe('this is global-2')
expect(result.globalType).toBe('global-2')
expect(timestamp).toBeLessThanOrEqual(new Date(result.createdAt as string).getTime())
expect(timestamp).toBeLessThanOrEqual(new Date(result.updatedAt as string).getTime())
const createdAt = new Date(result.createdAt as string).getTime()
result = (await payload.db.updateGlobal({
slug: 'global-2',
data: { text: 'this is global-2 but updated' },
})) as Global2 & { globalType: string }
expect(result.text).toBe('this is global-2 but updated')
expect(result.globalType).toBe('global-2')
expect(createdAt).toEqual(new Date(result.createdAt as string).getTime())
expect(createdAt).toBeLessThan(new Date(result.updatedAt as string).getTime())
})
it('payload.updateGlobal should have globalType, updatedAt, createdAt fields', async () => {
let timestamp = Date.now()
let result = (await payload.updateGlobal({
slug: 'global-3',
data: { text: 'this is global-3' },
})) as Global2 & { globalType: string }
expect(result.text).toBe('this is global-3')
expect(result.globalType).toBe('global-3')
expect(timestamp).toBeLessThanOrEqual(new Date(result.createdAt as string).getTime())
expect(timestamp).toBeLessThanOrEqual(new Date(result.updatedAt as string).getTime())
const createdAt = new Date(result.createdAt as string).getTime()
result = (await payload.updateGlobal({
slug: 'global-3',
data: { text: 'this is global-3 but updated' },
})) as Global2 & { globalType: string }
expect(result.text).toBe('this is global-3 but updated')
expect(result.globalType).toBe('global-3')
expect(createdAt).toEqual(new Date(result.createdAt as string).getTime())
expect(createdAt).toBeLessThan(new Date(result.updatedAt as string).getTime())
})
}) })

View File

@@ -52,9 +52,13 @@ export interface Config {
}; };
globals: { globals: {
global: Global; global: Global;
'global-2': Global2;
'global-3': Global3;
}; };
globalsSelect: { globalsSelect: {
global: GlobalSelect<false> | GlobalSelect<true>; global: GlobalSelect<false> | GlobalSelect<true>;
'global-2': Global2Select<false> | Global2Select<true>;
'global-3': Global3Select<false> | Global3Select<true>;
}; };
locale: 'en' | 'es'; locale: 'en' | 'es';
user: User & { user: User & {
@@ -753,6 +757,26 @@ export interface Global {
updatedAt?: string | null; updatedAt?: string | null;
createdAt?: string | null; createdAt?: string | null;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "global-2".
*/
export interface Global2 {
id: string;
text?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "global-3".
*/
export interface Global3 {
id: string;
text?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "global_select". * via the `definition` "global_select".
@@ -763,6 +787,26 @@ export interface GlobalSelect<T extends boolean = true> {
createdAt?: T; createdAt?: T;
globalType?: T; globalType?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "global-2_select".
*/
export interface Global2Select<T extends boolean = true> {
text?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "global-3_select".
*/
export interface Global3Select<T extends boolean = true> {
text?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth". * via the `definition` "auth".