From 5c16443431ec0573b3befba8b1b1079c922ea0bc Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Fri, 22 Aug 2025 05:41:07 -0700 Subject: [PATCH] fix: ensure updates to createdAt and updatedAt are respected (#13335) Previously, when manually setting `createdAt` or `updatedAt` in a `payload.db.*` or `payload.*` operation, the value may have been ignored. In some cases it was _impossible_ to change the `updatedAt` value, even when using direct db adapter calls. On top of that, this behavior sometimes differed between db adapters. For example, mongodb did accept `updatedAt` when calling `payload.db.updateVersion` - postgres ignored it. This PR changes this behavior to consistently respect `createdAt` and `updatedAt` values for `payload.db.*` operations. For `payload.*` operations, this also works with the following exception: - update operations do no respect `updatedAt`, as updates are commonly performed by spreading the old data, e.g. `payload.update({ data: {...oldData} })` - in these cases, we usually still want the `updatedAt` to be updated. If you need to get around this, you can use the `payload.db.updateOne` operation instead. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210919646303994 --- packages/db-mongodb/src/create.ts | 6 + packages/db-mongodb/src/createGlobal.ts | 6 + .../db-mongodb/src/createGlobalVersion.ts | 5 + packages/db-mongodb/src/createVersion.ts | 5 + .../migrateRelationshipsV2_V3.ts | 5 +- packages/db-mongodb/src/updateGlobal.ts | 2 + .../db-mongodb/src/updateGlobalVersion.ts | 2 + packages/db-mongodb/src/updateJobs.ts | 2 + packages/db-mongodb/src/updateMany.ts | 2 + packages/db-mongodb/src/updateOne.ts | 2 + packages/db-mongodb/src/updateVersion.ts | 2 + .../src/utilities/transform.spec.ts | 4 + .../db-mongodb/src/utilities/transform.ts | 6 + .../src/transform/write/traverseFields.ts | 26 +- packages/drizzle/src/upsertRow/index.ts | 4 + .../payload/src/auth/operations/logout.ts | 3 + .../payload/src/auth/operations/refresh.ts | 3 + .../src/auth/operations/resetPassword.ts | 3 + .../src/auth/operations/verifyEmail.ts | 3 + packages/payload/src/auth/sessions.ts | 3 + .../local/incrementLoginAttempts.ts | 3 + .../collections/operations/restoreVersion.ts | 2 + .../operations/utilities/update.ts | 2 + .../src/globals/operations/restoreVersion.ts | 2 + .../payload/src/globals/operations/update.ts | 3 + .../handleSchedules/defaultAfterSchedule.ts | 1 + .../payload/src/queues/utilities/updateJob.ts | 3 + test/database/getConfig.ts | 10 + test/database/int.spec.ts | 312 +++++++++++++++++- test/database/payload-types.ts | 21 ++ 30 files changed, 437 insertions(+), 16 deletions(-) diff --git a/packages/db-mongodb/src/create.ts b/packages/db-mongodb/src/create.ts index 5c7f04d82..82e65fcd7 100644 --- a/packages/db-mongodb/src/create.ts +++ b/packages/db-mongodb/src/create.ts @@ -17,10 +17,16 @@ export const create: Create = async function create( const options: CreateOptions = { session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } let doc + if (!data.createdAt) { + data.createdAt = new Date().toISOString() + } + transform({ adapter: this, data, diff --git a/packages/db-mongodb/src/createGlobal.ts b/packages/db-mongodb/src/createGlobal.ts index f91c4e5b3..10019d838 100644 --- a/packages/db-mongodb/src/createGlobal.ts +++ b/packages/db-mongodb/src/createGlobal.ts @@ -14,6 +14,10 @@ export const createGlobal: CreateGlobal = async function createGlobal( ) { const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug }) + if (!data.createdAt) { + ;(data as any).createdAt = new Date().toISOString() + } + transform({ adapter: this, data, @@ -24,6 +28,8 @@ export const createGlobal: CreateGlobal = async function createGlobal( const options: CreateOptions = { session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } let [result] = (await Model.create([data], options)) as any diff --git a/packages/db-mongodb/src/createGlobalVersion.ts b/packages/db-mongodb/src/createGlobalVersion.ts index c5910231e..c948881b5 100644 --- a/packages/db-mongodb/src/createGlobalVersion.ts +++ b/packages/db-mongodb/src/createGlobalVersion.ts @@ -25,6 +25,8 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo const options = { session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } const data = { @@ -37,6 +39,9 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo updatedAt, version: versionData, } + if (!data.createdAt) { + data.createdAt = new Date().toISOString() + } const fields = buildVersionGlobalFields(this.payload.config, globalConfig) diff --git a/packages/db-mongodb/src/createVersion.ts b/packages/db-mongodb/src/createVersion.ts index ec733baa8..a6a7b5c2c 100644 --- a/packages/db-mongodb/src/createVersion.ts +++ b/packages/db-mongodb/src/createVersion.ts @@ -29,6 +29,8 @@ export const createVersion: CreateVersion = async function createVersion( const options = { session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } const data = { @@ -41,6 +43,9 @@ export const createVersion: CreateVersion = async function createVersion( updatedAt, version: versionData, } + if (!data.createdAt) { + data.createdAt = new Date().toISOString() + } const fields = buildVersionCollectionFields(this.payload.config, collectionConfig) diff --git a/packages/db-mongodb/src/predefinedMigrations/migrateRelationshipsV2_V3.ts b/packages/db-mongodb/src/predefinedMigrations/migrateRelationshipsV2_V3.ts index 06665d9d6..ba5104547 100644 --- a/packages/db-mongodb/src/predefinedMigrations/migrateRelationshipsV2_V3.ts +++ b/packages/db-mongodb/src/predefinedMigrations/migrateRelationshipsV2_V3.ts @@ -63,7 +63,10 @@ const migrateModelWithBatching = async ({ }, }, })), - { session }, + { + session, // Timestamps are manually added by the write transform + timestamps: false, + }, ) skip += batchSize diff --git a/packages/db-mongodb/src/updateGlobal.ts b/packages/db-mongodb/src/updateGlobal.ts index 6e883de21..2ed7aa170 100644 --- a/packages/db-mongodb/src/updateGlobal.ts +++ b/packages/db-mongodb/src/updateGlobal.ts @@ -26,6 +26,8 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal( select, }), session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } transform({ adapter: this, data, fields, globalSlug, operation: 'write' }) diff --git a/packages/db-mongodb/src/updateGlobalVersion.ts b/packages/db-mongodb/src/updateGlobalVersion.ts index 6e26418ec..4e84a3ebc 100644 --- a/packages/db-mongodb/src/updateGlobalVersion.ts +++ b/packages/db-mongodb/src/updateGlobalVersion.ts @@ -39,6 +39,8 @@ export async function updateGlobalVersion( select, }), session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } const query = await buildQuery({ diff --git a/packages/db-mongodb/src/updateJobs.ts b/packages/db-mongodb/src/updateJobs.ts index 8ea4ecd57..5b9dab40b 100644 --- a/packages/db-mongodb/src/updateJobs.ts +++ b/packages/db-mongodb/src/updateJobs.ts @@ -36,6 +36,8 @@ export const updateJobs: UpdateJobs = async function updateMany( lean: true, new: true, session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } let query = await buildQuery({ diff --git a/packages/db-mongodb/src/updateMany.ts b/packages/db-mongodb/src/updateMany.ts index c0ef009ba..5f552a982 100644 --- a/packages/db-mongodb/src/updateMany.ts +++ b/packages/db-mongodb/src/updateMany.ts @@ -58,6 +58,8 @@ export const updateMany: UpdateMany = async function updateMany( select, }), session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } let query = await buildQuery({ diff --git a/packages/db-mongodb/src/updateOne.ts b/packages/db-mongodb/src/updateOne.ts index 20816512a..456fea976 100644 --- a/packages/db-mongodb/src/updateOne.ts +++ b/packages/db-mongodb/src/updateOne.ts @@ -38,6 +38,8 @@ export const updateOne: UpdateOne = async function updateOne( select, }), session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } const query = await buildQuery({ diff --git a/packages/db-mongodb/src/updateVersion.ts b/packages/db-mongodb/src/updateVersion.ts index 5e1b356ce..4b2cbc547 100644 --- a/packages/db-mongodb/src/updateVersion.ts +++ b/packages/db-mongodb/src/updateVersion.ts @@ -45,6 +45,8 @@ export const updateVersion: UpdateVersion = async function updateVersion( select, }), session: await getSession(this, req), + // Timestamps are manually added by the write transform + timestamps: false, } const query = await buildQuery({ diff --git a/packages/db-mongodb/src/utilities/transform.spec.ts b/packages/db-mongodb/src/utilities/transform.spec.ts index 85b5bdb49..4796473e3 100644 --- a/packages/db-mongodb/src/utilities/transform.spec.ts +++ b/packages/db-mongodb/src/utilities/transform.spec.ts @@ -395,6 +395,10 @@ describe('transform', () => { data, fields: config.collections[0].fields, }) + if ('updatedAt' in data) { + delete data.updatedAt + } + const flattenValuesAfter = Object.values(flattenRelationshipValues(data)) flattenValuesAfter.forEach((value, i) => { diff --git a/packages/db-mongodb/src/utilities/transform.ts b/packages/db-mongodb/src/utilities/transform.ts index 35a271877..ced99a2ad 100644 --- a/packages/db-mongodb/src/utilities/transform.ts +++ b/packages/db-mongodb/src/utilities/transform.ts @@ -548,4 +548,10 @@ export const transform = ({ parentIsLocalized, ref: data, }) + + if (operation === 'write') { + if (!data.updatedAt) { + data.updatedAt = new Date().toISOString() + } + } } diff --git a/packages/drizzle/src/transform/write/traverseFields.ts b/packages/drizzle/src/transform/write/traverseFields.ts index feb3b1766..56232ea8a 100644 --- a/packages/drizzle/src/transform/write/traverseFields.ts +++ b/packages/drizzle/src/transform/write/traverseFields.ts @@ -546,6 +546,19 @@ export const traverseFields = ({ valuesToTransform.forEach(({ localeKey, ref, value }) => { let formattedValue = value + if (field.type === 'date') { + if (fieldName === 'updatedAt' && !formattedValue) { + // let the db handle this + formattedValue = new Date().toISOString() + } else { + if (typeof value === 'number' && !Number.isNaN(value)) { + formattedValue = new Date(value).toISOString() + } else if (value instanceof Date) { + formattedValue = value.toISOString() + } + } + } + if (typeof value !== 'undefined') { if (value && field.type === 'point' && adapter.name !== 'sqlite') { formattedValue = sql`ST_GeomFromGeoJSON(${JSON.stringify(value)})` @@ -570,19 +583,6 @@ export const traverseFields = ({ formattedValue = sql.raw(`${columnName} + ${value.$inc}`) } - - if (field.type === 'date') { - if (typeof value === 'number' && !Number.isNaN(value)) { - formattedValue = new Date(value).toISOString() - } else if (value instanceof Date) { - formattedValue = value.toISOString() - } - } - } - - if (field.type === 'date' && fieldName === 'updatedAt') { - // let the db handle this - formattedValue = new Date().toISOString() } if (typeof formattedValue !== 'undefined') { diff --git a/packages/drizzle/src/upsertRow/index.ts b/packages/drizzle/src/upsertRow/index.ts index 52d686a55..00563c10b 100644 --- a/packages/drizzle/src/upsertRow/index.ts +++ b/packages/drizzle/src/upsertRow/index.ts @@ -42,6 +42,10 @@ export const upsertRow = async | TypeWithID>( upsertTarget, where, }: Args): Promise => { + if (operation === 'create' && !data.createdAt) { + data.createdAt = new Date().toISOString() + } + let insertedRow: Record = { id } if (id && shouldUseOptimizedUpsertRow({ data, fields })) { const { row } = transformForWrite({ diff --git a/packages/payload/src/auth/operations/logout.ts b/packages/payload/src/auth/operations/logout.ts index fddde87ac..c7a2e034c 100644 --- a/packages/payload/src/auth/operations/logout.ts +++ b/packages/payload/src/auth/operations/logout.ts @@ -73,6 +73,9 @@ export const logoutOperation = async (incomingArgs: Arguments): Promise userWithSessions.sessions = sessionsAfterLogout } + // Ensure updatedAt date is always updated + ;(userWithSessions as any).updatedAt = new Date().toISOString() + await req.payload.db.updateOne({ id: user.id, collection: collectionConfig.slug, diff --git a/packages/payload/src/auth/operations/refresh.ts b/packages/payload/src/auth/operations/refresh.ts index 037bcc4da..35b92e7e4 100644 --- a/packages/payload/src/auth/operations/refresh.ts +++ b/packages/payload/src/auth/operations/refresh.ts @@ -92,6 +92,9 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise const tokenExpInMs = collectionConfig.auth.tokenExpiration * 1000 existingSession.expiresAt = new Date(now.getTime() + tokenExpInMs) + // Ensure updatedAt date is always updated + user.updatedAt = new Date().toISOString() + await req.payload.db.updateOne({ id: user.id, collection: collectionConfig.slug, diff --git a/packages/payload/src/auth/operations/resetPassword.ts b/packages/payload/src/auth/operations/resetPassword.ts index 4cff6599f..767eaf922 100644 --- a/packages/payload/src/auth/operations/resetPassword.ts +++ b/packages/payload/src/auth/operations/resetPassword.ts @@ -131,6 +131,9 @@ export const resetPasswordOperation = async ( // Update new password // ///////////////////////////////////// + // Ensure updatedAt date is always updated + user.updatedAt = new Date().toISOString() + const doc = await payload.db.updateOne({ id: user.id, collection: collectionConfig.slug, diff --git a/packages/payload/src/auth/operations/verifyEmail.ts b/packages/payload/src/auth/operations/verifyEmail.ts index 69dd60644..83cf84817 100644 --- a/packages/payload/src/auth/operations/verifyEmail.ts +++ b/packages/payload/src/auth/operations/verifyEmail.ts @@ -46,6 +46,9 @@ export const verifyEmailOperation = async (args: Args): Promise => { throw new APIError('Verification token is invalid.', httpStatus.FORBIDDEN) } + // Ensure updatedAt date is always updated + user.updatedAt = new Date().toISOString() + await req.payload.db.updateOne({ id: user.id, collection: collection.config.slug, diff --git a/packages/payload/src/auth/sessions.ts b/packages/payload/src/auth/sessions.ts index 1073599e2..271c499fb 100644 --- a/packages/payload/src/auth/sessions.ts +++ b/packages/payload/src/auth/sessions.ts @@ -49,6 +49,9 @@ export const addSessionToUser = async ({ user.sessions.push(session) } + // Ensure updatedAt date is always updated + user.updatedAt = new Date().toISOString() + await payload.db.updateOne({ id: user.id, collection: collectionConfig.slug, diff --git a/packages/payload/src/auth/strategies/local/incrementLoginAttempts.ts b/packages/payload/src/auth/strategies/local/incrementLoginAttempts.ts index 4fb86bb5e..5f66a79a1 100644 --- a/packages/payload/src/auth/strategies/local/incrementLoginAttempts.ts +++ b/packages/payload/src/auth/strategies/local/incrementLoginAttempts.ts @@ -142,6 +142,9 @@ export const incrementLoginAttempts = async ({ user.sessions = currentUser.sessions + // Ensure updatedAt date is always updated + user.updatedAt = new Date().toISOString() + await payload.db.updateOne({ id: user.id, collection: collection.slug, diff --git a/packages/payload/src/collections/operations/restoreVersion.ts b/packages/payload/src/collections/operations/restoreVersion.ts index 1dd07cf2a..63218e7b6 100644 --- a/packages/payload/src/collections/operations/restoreVersion.ts +++ b/packages/payload/src/collections/operations/restoreVersion.ts @@ -258,6 +258,8 @@ export const restoreVersionOperation = async ( select: incomingSelect, }) + // Ensure updatedAt date is always updated + result.updatedAt = new Date().toISOString() result = await req.payload.db.updateOne({ id: parentDocID, collection: collectionConfig.slug, diff --git a/packages/payload/src/collections/operations/utilities/update.ts b/packages/payload/src/collections/operations/utilities/update.ts index c9a0f9f28..7f42074a7 100644 --- a/packages/payload/src/collections/operations/utilities/update.ts +++ b/packages/payload/src/collections/operations/utilities/update.ts @@ -293,6 +293,8 @@ export const updateDocument = async < // ///////////////////////////////////// if (!shouldSaveDraft) { + // Ensure updatedAt date is always updated + dataToUpdate.updatedAt = new Date().toISOString() result = await req.payload.db.updateOne({ id, collection: collectionConfig.slug, diff --git a/packages/payload/src/globals/operations/restoreVersion.ts b/packages/payload/src/globals/operations/restoreVersion.ts index fcff8f8e2..dee79bbc3 100644 --- a/packages/payload/src/globals/operations/restoreVersion.ts +++ b/packages/payload/src/globals/operations/restoreVersion.ts @@ -86,6 +86,8 @@ export const restoreVersionOperation = async = any let result = rawVersion.version if (global) { + // Ensure updatedAt date is always updated + result.updatedAt = new Date().toISOString() result = await payload.db.updateGlobal({ slug: globalConfig.slug, data: result, diff --git a/packages/payload/src/globals/operations/update.ts b/packages/payload/src/globals/operations/update.ts index ed10f361b..19d70f1d5 100644 --- a/packages/payload/src/globals/operations/update.ts +++ b/packages/payload/src/globals/operations/update.ts @@ -256,6 +256,9 @@ export const updateOperation = async < result.createdAt = new Date().toISOString() } + // Ensure updatedAt date is always updated + result.updatedAt = new Date().toISOString() + if (globalExists) { result = await payload.db.updateGlobal({ slug, diff --git a/packages/payload/src/queues/operations/handleSchedules/defaultAfterSchedule.ts b/packages/payload/src/queues/operations/handleSchedules/defaultAfterSchedule.ts index 4627c407c..e63aee2fe 100644 --- a/packages/payload/src/queues/operations/handleSchedules/defaultAfterSchedule.ts +++ b/packages/payload/src/queues/operations/handleSchedules/defaultAfterSchedule.ts @@ -40,6 +40,7 @@ export const defaultAfterSchedule: AfterScheduleFn = async ({ jobStats, queueabl }, }, }, + updatedAt: new Date().toISOString(), } as JobStats, req, returning: false, diff --git a/packages/payload/src/queues/utilities/updateJob.ts b/packages/payload/src/queues/utilities/updateJob.ts index a8a4ff69e..c4ef03343 100644 --- a/packages/payload/src/queues/utilities/updateJob.ts +++ b/packages/payload/src/queues/utilities/updateJob.ts @@ -83,6 +83,9 @@ export async function updateJobs({ : undefined, } + // Ensure updatedAt date is always updated + data.updatedAt = new Date().toISOString() + const args: UpdateJobsArgs = id ? { id, diff --git a/test/database/getConfig.ts b/test/database/getConfig.ts index 4d2c85d43..2f0de7501 100644 --- a/test/database/getConfig.ts +++ b/test/database/getConfig.ts @@ -36,6 +36,16 @@ export const getConfig: () => Partial = () => ({ }, }, collections: [ + { + slug: 'noTimeStamps', + timestamps: false, + fields: [ + { + type: 'text', + name: 'title', + }, + ], + }, { slug: 'categories', versions: { drafts: true }, diff --git a/test/database/int.spec.ts b/test/database/int.spec.ts index c4cbff439..d37c16bf0 100644 --- a/test/database/int.spec.ts +++ b/test/database/int.spec.ts @@ -49,6 +49,8 @@ const collection = postsSlug const title = 'title' process.env.PAYLOAD_CONFIG_PATH = path.join(dirname, 'config.ts') +const itMongo = process.env.PAYLOAD_DATABASE?.startsWith('mongodb') ? it : it.skip + describe('database', () => { beforeAll(async () => { process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit @@ -224,6 +226,12 @@ describe('database', () => { const createdAtDate = new Date(result.createdAt) expect(createdAtDate.getMilliseconds()).toBeDefined() + + // Cleanup, as this test suite does not use clearAndSeedEverything + await payload.db.deleteMany({ + collection: postsSlug, + where: {}, + }) }) it('should allow createdAt to be set in create', async () => { @@ -243,9 +251,15 @@ describe('database', () => { expect(result.createdAt).toStrictEqual(createdAt) expect(doc.createdAt).toStrictEqual(createdAt) + + // Cleanup, as this test suite does not use clearAndSeedEverything + await payload.db.deleteMany({ + collection: postsSlug, + where: {}, + }) }) - it('updatedAt cannot be set in create', async () => { + it('should allow updatedAt to be set in create', async () => { const updatedAt = new Date('2022-01-01T00:00:00.000Z').toISOString() const result = await payload.create({ collection: postsSlug, @@ -255,8 +269,302 @@ describe('database', () => { }, }) - expect(result.updatedAt).not.toStrictEqual(updatedAt) + expect(result.updatedAt).toStrictEqual(updatedAt) + + // Cleanup, as this test suite does not use clearAndSeedEverything + await payload.db.deleteMany({ + collection: postsSlug, + where: {}, + }) }) + it('should allow createdAt to be set in update', async () => { + const post = await payload.create({ + collection: postsSlug, + data: { + title: 'hello', + }, + }) + const createdAt = new Date('2021-01-01T00:00:00.000Z').toISOString() + + const result: any = await payload.db.updateOne({ + collection: postsSlug, + id: post.id, + data: { + createdAt, + }, + }) + + const doc = await payload.findByID({ + id: result.id, + collection: postsSlug, + }) + + expect(doc.createdAt).toStrictEqual(createdAt) + + // Cleanup, as this test suite does not use clearAndSeedEverything + await payload.db.deleteMany({ + collection: postsSlug, + where: {}, + }) + }) + + it('should allow updatedAt to be set in update', async () => { + const post = await payload.create({ + collection: postsSlug, + data: { + title: 'hello', + }, + }) + const updatedAt = new Date('2021-01-01T00:00:00.000Z').toISOString() + + const result: any = await payload.db.updateOne({ + collection: postsSlug, + id: post.id, + data: { + updatedAt, + }, + }) + + const doc = await payload.findByID({ + id: result.id, + collection: postsSlug, + }) + + expect(doc.updatedAt).toStrictEqual(updatedAt) + + // Cleanup, as this test suite does not use clearAndSeedEverything + await payload.db.deleteMany({ + collection: postsSlug, + where: {}, + }) + }) + + it('should allow createdAt to be set in updateVersion', async () => { + const category = await payload.create({ + collection: 'categories', + data: { + title: 'hello', + }, + }) + await payload.update({ + collection: 'categories', + id: category.id, + data: { + title: 'hello2', + }, + }) + const versions = await payload.findVersions({ + collection: 'categories', + depth: 0, + sort: '-createdAt', + }) + const createdAt = new Date('2021-01-01T00:00:00.000Z').toISOString() + + for (const version of versions.docs) { + await payload.db.updateVersion({ + id: version.id, + collection: 'categories', + versionData: { + ...version.version, + createdAt, + }, + }) + } + + const updatedVersions = await payload.findVersions({ + collection: 'categories', + depth: 0, + sort: '-createdAt', + }) + expect(updatedVersions.docs).toHaveLength(2) + for (const version of updatedVersions.docs) { + expect(version.createdAt).toStrictEqual(createdAt) + } + + // Cleanup, as this test suite does not use clearAndSeedEverything + await payload.db.deleteMany({ + collection: 'categories', + where: {}, + }) + await payload.db.deleteVersions({ + collection: 'categories', + where: {}, + }) + }) + + it('should allow updatedAt to be set in updateVersion', async () => { + const category = await payload.create({ + collection: 'categories', + data: { + title: 'hello', + }, + }) + await payload.update({ + collection: 'categories', + id: category.id, + data: { + title: 'hello2', + }, + }) + const versions = await payload.findVersions({ + collection: 'categories', + depth: 0, + sort: '-createdAt', + }) + const updatedAt = new Date('2021-01-01T00:00:00.000Z').toISOString() + + for (const version of versions.docs) { + await payload.db.updateVersion({ + id: version.id, + collection: 'categories', + versionData: { + ...version.version, + updatedAt, + }, + }) + } + + const updatedVersions = await payload.findVersions({ + collection: 'categories', + depth: 0, + sort: '-updatedAt', + }) + expect(updatedVersions.docs).toHaveLength(2) + for (const version of updatedVersions.docs) { + expect(version.updatedAt).toStrictEqual(updatedAt) + } + + // Cleanup, as this test suite does not use clearAndSeedEverything + await payload.db.deleteMany({ + collection: 'categories', + where: {}, + }) + await payload.db.deleteVersions({ + collection: 'categories', + where: {}, + }) + }) + + async function noTimestampsTestLocalAPI() { + const createdDoc: any = await payload.create({ + collection: 'noTimeStamps', + data: { + title: 'hello', + }, + }) + expect(createdDoc.createdAt).toBeUndefined() + expect(createdDoc.updatedAt).toBeUndefined() + + const updated: any = await payload.update({ + collection: 'noTimeStamps', + id: createdDoc.id, + data: { + title: 'updated', + }, + }) + expect(updated.createdAt).toBeUndefined() + expect(updated.updatedAt).toBeUndefined() + + const date = new Date('2021-01-01T00:00:00.000Z').toISOString() + const createdDocWithTimestamps: any = await payload.create({ + collection: 'noTimeStamps', + data: { + title: 'hello', + createdAt: date, + updatedAt: date, + }, + }) + expect(createdDocWithTimestamps.createdAt).toBeUndefined() + expect(createdDocWithTimestamps.updatedAt).toBeUndefined() + + const updatedDocWithTimestamps: any = await payload.update({ + collection: 'noTimeStamps', + id: createdDocWithTimestamps.id, + data: { + title: 'updated', + createdAt: date, + updatedAt: date, + }, + }) + expect(updatedDocWithTimestamps.createdAt).toBeUndefined() + expect(updatedDocWithTimestamps.updatedAt).toBeUndefined() + } + + async function noTimestampsTestDB(aa) { + const createdDoc: any = await payload.db.create({ + collection: 'noTimeStamps', + data: { + title: 'hello', + }, + }) + expect(createdDoc.createdAt).toBeUndefined() + expect(createdDoc.updatedAt).toBeUndefined() + + const updated: any = await payload.db.updateOne({ + collection: 'noTimeStamps', + id: createdDoc.id, + data: { + title: 'updated', + }, + }) + expect(updated.createdAt).toBeUndefined() + expect(updated.updatedAt).toBeUndefined() + + const date = new Date('2021-01-01T00:00:00.000Z').toISOString() + const createdDocWithTimestamps: any = await payload.db.create({ + collection: 'noTimeStamps', + data: { + title: 'hello', + createdAt: date, + updatedAt: date, + }, + }) + expect(createdDocWithTimestamps.createdAt).toBeUndefined() + expect(createdDocWithTimestamps.updatedAt).toBeUndefined() + + const updatedDocWithTimestamps: any = await payload.db.updateOne({ + collection: 'noTimeStamps', + id: createdDocWithTimestamps.id, + data: { + title: 'updated', + createdAt: date, + updatedAt: date, + }, + }) + expect(updatedDocWithTimestamps.createdAt).toBeUndefined() + expect(updatedDocWithTimestamps.updatedAt).toBeUndefined() + } + + // eslint-disable-next-line jest/expect-expect + it('ensure timestamps are not created in update or create when timestamps are disabled', async () => { + await noTimestampsTestLocalAPI() + }) + + // eslint-disable-next-line jest/expect-expect + it('ensure timestamps are not created in db adapter update or create when timestamps are disabled', async () => { + await noTimestampsTestDB(true) + }) + + itMongo( + 'ensure timestamps are not created in update or create when timestamps are disabled even with allowAdditionalKeys true', + async () => { + const originalAllowAdditionalKeys = payload.db.allowAdditionalKeys + payload.db.allowAdditionalKeys = true + await noTimestampsTestLocalAPI() + payload.db.allowAdditionalKeys = originalAllowAdditionalKeys + }, + ) + + itMongo( + 'ensure timestamps are not created in db adapter update or create when timestamps are disabled even with allowAdditionalKeys true', + async () => { + const originalAllowAdditionalKeys = payload.db.allowAdditionalKeys + payload.db.allowAdditionalKeys = true + await noTimestampsTestDB() + + payload.db.allowAdditionalKeys = originalAllowAdditionalKeys + }, + ) }) describe('Data strictness', () => { diff --git a/test/database/payload-types.ts b/test/database/payload-types.ts index 18b196b38..881512b4b 100644 --- a/test/database/payload-types.ts +++ b/test/database/payload-types.ts @@ -67,6 +67,7 @@ export interface Config { }; blocks: {}; collections: { + noTimeStamps: NoTimeStamp; categories: Category; simple: Simple; 'categories-custom-id': CategoriesCustomId; @@ -94,6 +95,7 @@ export interface Config { }; collectionsJoins: {}; collectionsSelect: { + noTimeStamps: NoTimeStampsSelect | NoTimeStampsSelect; categories: CategoriesSelect | CategoriesSelect; simple: SimpleSelect | SimpleSelect; 'categories-custom-id': CategoriesCustomIdSelect | CategoriesCustomIdSelect; @@ -163,6 +165,14 @@ export interface UserAuthOperations { password: string; }; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "noTimeStamps". + */ +export interface NoTimeStamp { + id: string; + title?: string | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "categories". @@ -617,6 +627,10 @@ export interface User { export interface PayloadLockedDocument { id: string; document?: + | ({ + relationTo: 'noTimeStamps'; + value: string | NoTimeStamp; + } | null) | ({ relationTo: 'categories'; value: string | Category; @@ -743,6 +757,13 @@ export interface PayloadMigration { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "noTimeStamps_select". + */ +export interface NoTimeStampsSelect { + title?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "categories_select".