Skip to content

Commit

Permalink
feat: consolidates logic in update and updateByID operations (#9998)
Browse files Browse the repository at this point in the history
### What?
Consolidates logic in update and updateByID. These two operations used a
lot of the same core functionality. This is a QOL improvement for future
features/fixes on each operation. You will not need to make changes to
both operations now.

### Why?
Recently we released a feature for `publishSpecificLocale` and that was
only implemented on the updateByID operation. I think future
enhancements and fixes may suffer the same treatment.

### How?
Moves shared logic into a new file with a function called
`updateDocument`.
  • Loading branch information
JarrodMFlesch authored Jan 13, 2025
1 parent 04a8083 commit 690e99f
Show file tree
Hide file tree
Showing 3 changed files with 428 additions and 529 deletions.
266 changes: 26 additions & 240 deletions packages/payload/src/collections/operations/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,18 @@ import type {
SelectFromCollectionSlug,
} from '../config/types.js'

import { ensureUsernameOrEmail } from '../../auth/ensureUsernameOrEmail.js'
import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
import { APIError } from '../../errors/index.js'
import { afterChange } from '../../fields/hooks/afterChange/index.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { beforeChange } from '../../fields/hooks/beforeChange/index.js'
import { beforeValidate } from '../../fields/hooks/beforeValidate/index.js'
import { deleteAssociatedFiles } from '../../uploads/deleteAssociatedFiles.js'
import { generateFileData } from '../../uploads/generateFileData.js'
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles.js'
import { uploadFiles } from '../../uploads/uploadFiles.js'
import { checkDocumentLockStatus } from '../../utilities/checkDocumentLockStatus.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
import { saveVersion } from '../../versions/saveVersion.js'
import { updateDocument } from './utilities/update.js'
import { buildAfterOperation } from './utils.js'

export type Arguments<TSlug extends CollectionSlug> = {
Expand All @@ -47,6 +39,7 @@ export type Arguments<TSlug extends CollectionSlug> = {
overrideLock?: boolean
overwriteExistingFiles?: boolean
populate?: PopulateType
publishSpecificLocale?: string
req: PayloadRequest
select?: SelectType
showHiddenFields?: boolean
Expand Down Expand Up @@ -91,6 +84,7 @@ export const updateOperation = async <
overrideLock,
overwriteExistingFiles = false,
populate,
publishSpecificLocale,
req: {
fallbackLocale,
locale,
Expand Down Expand Up @@ -172,7 +166,7 @@ export const updateOperation = async <
// Generate data for all files and sizes
// /////////////////////////////////////

const { data: newFileData, files: filesToUpload } = await generateFileData({
const { data, files: filesToUpload } = await generateFileData({
collection,
config,
data: bulkUpdateData,
Expand All @@ -184,251 +178,37 @@ export const updateOperation = async <

const errors = []

const promises = docs.map(async (doc) => {
const { id } = doc
let data = {
...newFileData,
...bulkUpdateData,
}
const promises = docs.map(async (docWithLocales) => {
const { id } = docWithLocales

try {
// /////////////////////////////////////
// Handle potentially locked documents
// /////////////////////////////////////

await checkDocumentLockStatus({
// ///////////////////////////////////////////////
// Update document, runs all document level hooks
// ///////////////////////////////////////////////
const updatedDoc = await updateDocument({
id,
collectionSlug: collectionConfig.slug,
lockErrorMessage: `Document with ID ${id} is currently locked by another user and cannot be updated.`,
overrideLock,
req,
})

const originalDoc = await afterRead({
collection: collectionConfig,
context: req.context,
depth: 0,
doc,
draft: draftArg,
fallbackLocale,
global: null,
locale,
overrideAccess: true,
req,
showHiddenFields: true,
})

await deleteAssociatedFiles({
accessResults: accessResult,
autosave: false,
collectionConfig,
config,
doc,
files: filesToUpload,
overrideDelete: false,
req,
})

if (args.collection.config.auth) {
ensureUsernameOrEmail<TSlug>({
authOptions: args.collection.config.auth,
collectionSlug: args.collection.config.slug,
data: args.data,
operation: 'update',
originalDoc,
req: args.req,
})
}

// /////////////////////////////////////
// beforeValidate - Fields
// /////////////////////////////////////

data = await beforeValidate<DeepPartial<DataFromCollectionSlug<TSlug>>>({
id,
collection: collectionConfig,
context: req.context,
data,
doc: originalDoc,
global: null,
operation: 'update',
overrideAccess,
req,
})

// /////////////////////////////////////
// beforeValidate - Collection
// /////////////////////////////////////

await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
await priorHook

data =
(await hook({
collection: collectionConfig,
context: req.context,
data,
operation: 'update',
originalDoc,
req,
})) || data
}, Promise.resolve())

// /////////////////////////////////////
// Write files to local storage
// /////////////////////////////////////

if (!collectionConfig.upload.disableLocalStorage) {
await uploadFiles(payload, filesToUpload, req)
}

// /////////////////////////////////////
// beforeChange - Collection
// /////////////////////////////////////

await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
await priorHook

data =
(await hook({
collection: collectionConfig,
context: req.context,
data,
operation: 'update',
originalDoc,
req,
})) || data
}, Promise.resolve())

// /////////////////////////////////////
// beforeChange - Fields
// /////////////////////////////////////

let result = await beforeChange({
id,
collection: collectionConfig,
context: req.context,
data,
doc: originalDoc,
docWithLocales: doc,
global: null,
operation: 'update',
req,
skipValidation:
shouldSaveDraft &&
collectionConfig.versions.drafts &&
!collectionConfig.versions.drafts.validate &&
data._status !== 'published',
})

// /////////////////////////////////////
// Update
// /////////////////////////////////////

if (!shouldSaveDraft || data._status === 'published') {
result = await req.payload.db.updateOne({
id,
collection: collectionConfig.slug,
data: result,
locale,
req,
select,
})
}

// /////////////////////////////////////
// Create version
// /////////////////////////////////////

if (collectionConfig.versions) {
result = await saveVersion({
id,
collection: collectionConfig,
docWithLocales: result,
payload,
req,
select,
})
}

// /////////////////////////////////////
// afterRead - Fields
// /////////////////////////////////////

result = await afterRead({
collection: collectionConfig,
context: req.context,
depth,
doc: result,
draft: draftArg,
fallbackLocale: null,
global: null,
docWithLocales,
draftArg,
fallbackLocale,
filesToUpload,
locale,
overrideAccess,
overrideLock,
payload,
populate,
publishSpecificLocale,
req,
select,
showHiddenFields,
})

// /////////////////////////////////////
// afterRead - Collection
// /////////////////////////////////////

await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
await priorHook

result =
(await hook({
collection: collectionConfig,
context: req.context,
doc: result,
req,
})) || result
}, Promise.resolve())

// /////////////////////////////////////
// afterChange - Fields
// /////////////////////////////////////

result = await afterChange({
collection: collectionConfig,
context: req.context,
data,
doc: result,
global: null,
operation: 'update',
previousDoc: originalDoc,
req,
})

// /////////////////////////////////////
// afterChange - Collection
// /////////////////////////////////////

await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
await priorHook

result =
(await hook({
collection: collectionConfig,
context: req.context,
doc: result,
operation: 'update',
previousDoc: originalDoc,
req,
})) || result
}, Promise.resolve())

await unlinkTempFiles({
collectionConfig,
config,
req,
})

// /////////////////////////////////////
// Return results
// /////////////////////////////////////

return result
return updatedDoc
} catch (error) {
errors.push({
id,
Expand All @@ -438,6 +218,12 @@ export const updateOperation = async <
return null
})

await unlinkTempFiles({
collectionConfig,
config,
req,
})

const awaitedDocs = await Promise.all(promises)

let result = {
Expand Down
Loading

0 comments on commit 690e99f

Please sign in to comment.