feat: consolidates logic in update and updateByID operations (#9998)
### 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`.
This commit is contained in:
@@ -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> = {
|
||||
@@ -47,6 +39,7 @@ export type Arguments<TSlug extends CollectionSlug> = {
|
||||
overrideLock?: boolean
|
||||
overwriteExistingFiles?: boolean
|
||||
populate?: PopulateType
|
||||
publishSpecificLocale?: string
|
||||
req: PayloadRequest
|
||||
select?: SelectType
|
||||
showHiddenFields?: boolean
|
||||
@@ -91,6 +84,7 @@ export const updateOperation = async <
|
||||
overrideLock,
|
||||
overwriteExistingFiles = false,
|
||||
populate,
|
||||
publishSpecificLocale,
|
||||
req: {
|
||||
fallbackLocale,
|
||||
locale,
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -438,6 +218,12 @@ export const updateOperation = async <
|
||||
return null
|
||||
})
|
||||
|
||||
await unlinkTempFiles({
|
||||
collectionConfig,
|
||||
config,
|
||||
req,
|
||||
})
|
||||
|
||||
const awaitedDocs = await Promise.all(promises)
|
||||
|
||||
let result = {
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { DeepPartial } from 'ts-essentials'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import type { FindOneArgs } from '../../database/types.js'
|
||||
import type { Args } from '../../fields/hooks/beforeChange/index.js'
|
||||
import type { CollectionSlug } from '../../index.js'
|
||||
import type {
|
||||
PayloadRequest,
|
||||
@@ -13,31 +12,21 @@ import type {
|
||||
} from '../../types/index.js'
|
||||
import type {
|
||||
Collection,
|
||||
DataFromCollectionSlug,
|
||||
RequiredDataFromCollectionSlug,
|
||||
SelectFromCollectionSlug,
|
||||
} from '../config/types.js'
|
||||
|
||||
import { ensureUsernameOrEmail } from '../../auth/ensureUsernameOrEmail.js'
|
||||
import executeAccess from '../../auth/executeAccess.js'
|
||||
import { generatePasswordSaltHash } from '../../auth/strategies/local/generatePasswordSaltHash.js'
|
||||
import { hasWhereAccessResult } from '../../auth/types.js'
|
||||
import { combineQueries } from '../../database/combineQueries.js'
|
||||
import { APIError, Forbidden, NotFound } 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 { getLatestCollectionVersion } from '../../versions/getLatestCollectionVersion.js'
|
||||
import { saveVersion } from '../../versions/saveVersion.js'
|
||||
import { updateDocument } from './utilities/update.js'
|
||||
import { buildAfterOperation } from './utils.js'
|
||||
|
||||
export type Arguments<TSlug extends CollectionSlug> = {
|
||||
@@ -118,10 +107,7 @@ export const updateByIDOperation = async <
|
||||
throw new APIError('Missing ID of document to update.', httpStatus.BAD_REQUEST)
|
||||
}
|
||||
|
||||
let { data } = args
|
||||
const password = data?.password
|
||||
const shouldSaveDraft = Boolean(draftArg && collectionConfig.versions.drafts)
|
||||
const shouldSavePassword = Boolean(password && collectionConfig.auth && !shouldSaveDraft)
|
||||
const { data } = args
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Access
|
||||
@@ -158,43 +144,6 @@ export const updateByIDOperation = async <
|
||||
throw new Forbidden(req.t)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Handle potentially locked documents
|
||||
// /////////////////////////////////////
|
||||
|
||||
await checkDocumentLockStatus({
|
||||
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: docWithLocales,
|
||||
draft: draftArg,
|
||||
fallbackLocale: null,
|
||||
global: null,
|
||||
locale,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
showHiddenFields: true,
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Generate data for all files and sizes
|
||||
// /////////////////////////////////////
|
||||
@@ -209,266 +158,50 @@ export const updateByIDOperation = async <
|
||||
throwOnMissingFile: false,
|
||||
})
|
||||
|
||||
data = newFileData
|
||||
// ///////////////////////////////////////////////
|
||||
// Update document, runs all document level hooks
|
||||
// ///////////////////////////////////////////////
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete any associated files
|
||||
// /////////////////////////////////////
|
||||
|
||||
await deleteAssociatedFiles({
|
||||
let result = await updateDocument<TSlug, TSelect>({
|
||||
id,
|
||||
accessResults,
|
||||
autosave,
|
||||
collectionConfig,
|
||||
config,
|
||||
doc: docWithLocales,
|
||||
files: filesToUpload,
|
||||
overrideDelete: false,
|
||||
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 publishedDocWithLocales = docWithLocales
|
||||
let versionSnapshotResult
|
||||
|
||||
const beforeChangeArgs: Args<DataFromCollectionSlug<TSlug>> = {
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data: { ...data, id },
|
||||
doc: originalDoc,
|
||||
docWithLocales: undefined,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
req,
|
||||
skipValidation:
|
||||
shouldSaveDraft &&
|
||||
collectionConfig.versions.drafts &&
|
||||
!collectionConfig.versions.drafts.validate &&
|
||||
data._status !== 'published',
|
||||
}
|
||||
|
||||
if (publishSpecificLocale) {
|
||||
versionSnapshotResult = await beforeChange({
|
||||
...beforeChangeArgs,
|
||||
docWithLocales,
|
||||
})
|
||||
|
||||
const lastPublished = await getLatestCollectionVersion({
|
||||
id,
|
||||
config: collectionConfig,
|
||||
payload,
|
||||
published: true,
|
||||
query: findOneArgs,
|
||||
req,
|
||||
})
|
||||
|
||||
publishedDocWithLocales = lastPublished ? lastPublished : {}
|
||||
}
|
||||
|
||||
let result = await beforeChange({
|
||||
...beforeChangeArgs,
|
||||
docWithLocales: publishedDocWithLocales,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Handle potential password update
|
||||
// /////////////////////////////////////
|
||||
|
||||
const dataToUpdate: Record<string, unknown> = { ...result }
|
||||
|
||||
if (shouldSavePassword && typeof password === 'string') {
|
||||
const { hash, salt } = await generatePasswordSaltHash({
|
||||
collection: collectionConfig,
|
||||
password,
|
||||
req,
|
||||
})
|
||||
dataToUpdate.salt = salt
|
||||
dataToUpdate.hash = hash
|
||||
delete dataToUpdate.password
|
||||
delete data.password
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Update
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (!shouldSaveDraft || data._status === 'published') {
|
||||
result = await req.payload.db.updateOne({
|
||||
id,
|
||||
collection: collectionConfig.slug,
|
||||
data: dataToUpdate,
|
||||
locale,
|
||||
req,
|
||||
select,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Create version
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
result = await saveVersion({
|
||||
id,
|
||||
autosave,
|
||||
collection: collectionConfig,
|
||||
docWithLocales: result,
|
||||
draft: shouldSaveDraft,
|
||||
payload,
|
||||
publishSpecificLocale,
|
||||
req,
|
||||
select,
|
||||
snapshot: versionSnapshotResult,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data: newFileData,
|
||||
depth,
|
||||
doc: result,
|
||||
draft: draftArg,
|
||||
docWithLocales,
|
||||
draftArg,
|
||||
fallbackLocale,
|
||||
global: null,
|
||||
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())
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await buildAfterOperation({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'updateByID',
|
||||
result,
|
||||
})
|
||||
|
||||
await unlinkTempFiles({
|
||||
collectionConfig,
|
||||
config,
|
||||
req,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = (await buildAfterOperation({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'updateByID',
|
||||
result,
|
||||
})) as TransformCollectionWithSelect<TSlug, TSelect>
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
// /////////////////////////////////////
|
||||
@@ -477,7 +210,7 @@ export const updateByIDOperation = async <
|
||||
await commitTransaction(req)
|
||||
}
|
||||
|
||||
return result as TransformCollectionWithSelect<TSlug, TSelect>
|
||||
return result
|
||||
} catch (error: unknown) {
|
||||
await killTransaction(args.req)
|
||||
throw error
|
||||
|
||||
380
packages/payload/src/collections/operations/utilities/update.ts
Normal file
380
packages/payload/src/collections/operations/utilities/update.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
import type { DeepPartial } from 'ts-essentials'
|
||||
|
||||
import type { Args } from '../../../fields/hooks/beforeChange/index.js'
|
||||
import type { AccessResult, CollectionSlug, FileToSave, SanitizedConfig } from '../../../index.js'
|
||||
import type {
|
||||
Payload,
|
||||
PayloadRequest,
|
||||
PopulateType,
|
||||
SelectType,
|
||||
TransformCollectionWithSelect,
|
||||
} from '../../../types/index.js'
|
||||
import type {
|
||||
DataFromCollectionSlug,
|
||||
SanitizedCollectionConfig,
|
||||
SelectFromCollectionSlug,
|
||||
} from '../../config/types.js'
|
||||
|
||||
import { ensureUsernameOrEmail } from '../../../auth/ensureUsernameOrEmail.js'
|
||||
import { generatePasswordSaltHash } from '../../../auth/strategies/local/generatePasswordSaltHash.js'
|
||||
import { combineQueries } from '../../../database/combineQueries.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 { uploadFiles } from '../../../uploads/uploadFiles.js'
|
||||
import { checkDocumentLockStatus } from '../../../utilities/checkDocumentLockStatus.js'
|
||||
import { getLatestCollectionVersion } from '../../../versions/getLatestCollectionVersion.js'
|
||||
import { saveVersion } from '../../../versions/saveVersion.js'
|
||||
|
||||
export type SharedUpdateDocumentArgs<TSlug extends CollectionSlug> = {
|
||||
accessResults: AccessResult
|
||||
autosave: boolean
|
||||
collectionConfig: SanitizedCollectionConfig
|
||||
config: SanitizedConfig
|
||||
data: DeepPartial<DataFromCollectionSlug<TSlug>>
|
||||
depth: number
|
||||
docWithLocales: any
|
||||
draftArg: boolean
|
||||
fallbackLocale: string
|
||||
filesToUpload: FileToSave[]
|
||||
id: number | string
|
||||
locale: string
|
||||
overrideAccess: boolean
|
||||
overrideLock: boolean
|
||||
payload: Payload
|
||||
populate?: PopulateType
|
||||
publishSpecificLocale?: string
|
||||
req: PayloadRequest
|
||||
select: SelectType
|
||||
showHiddenFields: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to update a document in the DB and return the result.
|
||||
*
|
||||
* It runs the following hooks in order:
|
||||
* - beforeValidate - Fields
|
||||
* - beforeValidate - Collection
|
||||
* - beforeChange - Collection
|
||||
* - beforeChange - Fields
|
||||
* - afterRead - Fields
|
||||
* - afterRead - Collection
|
||||
* - afterChange - Fields
|
||||
* - afterChange - Collection
|
||||
*/
|
||||
export const updateDocument = async <
|
||||
TSlug extends CollectionSlug,
|
||||
TSelect extends SelectFromCollectionSlug<TSlug> = SelectType,
|
||||
>({
|
||||
id,
|
||||
accessResults,
|
||||
autosave,
|
||||
collectionConfig,
|
||||
config,
|
||||
data,
|
||||
depth,
|
||||
docWithLocales,
|
||||
draftArg,
|
||||
fallbackLocale,
|
||||
filesToUpload,
|
||||
locale,
|
||||
overrideAccess,
|
||||
overrideLock,
|
||||
payload,
|
||||
populate,
|
||||
publishSpecificLocale,
|
||||
req,
|
||||
select,
|
||||
showHiddenFields,
|
||||
}: SharedUpdateDocumentArgs<TSlug>): Promise<TransformCollectionWithSelect<TSlug, TSelect>> => {
|
||||
const password = data?.password
|
||||
const shouldSaveDraft =
|
||||
Boolean(draftArg && collectionConfig.versions.drafts) && data._status !== 'published'
|
||||
const shouldSavePassword = Boolean(password && collectionConfig.auth && !shouldSaveDraft)
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Handle potentially locked documents
|
||||
// /////////////////////////////////////
|
||||
|
||||
await checkDocumentLockStatus({
|
||||
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: docWithLocales,
|
||||
draft: draftArg,
|
||||
fallbackLocale: id ? null : fallbackLocale,
|
||||
global: null,
|
||||
locale,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
showHiddenFields: true,
|
||||
})
|
||||
|
||||
if (collectionConfig.auth) {
|
||||
ensureUsernameOrEmail<TSlug>({
|
||||
authOptions: collectionConfig.auth,
|
||||
collectionSlug: collectionConfig.slug,
|
||||
data,
|
||||
operation: 'update',
|
||||
originalDoc,
|
||||
req,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete any associated files
|
||||
// /////////////////////////////////////
|
||||
|
||||
await deleteAssociatedFiles({
|
||||
collectionConfig,
|
||||
config,
|
||||
doc: docWithLocales,
|
||||
files: filesToUpload,
|
||||
overrideDelete: false,
|
||||
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 publishedDocWithLocales = docWithLocales
|
||||
let versionSnapshotResult
|
||||
|
||||
const beforeChangeArgs: Args<DataFromCollectionSlug<TSlug>> = {
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data: { ...data, id },
|
||||
doc: originalDoc,
|
||||
docWithLocales: undefined,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
req,
|
||||
skipValidation:
|
||||
shouldSaveDraft &&
|
||||
collectionConfig.versions.drafts &&
|
||||
!collectionConfig.versions.drafts.validate,
|
||||
}
|
||||
|
||||
if (publishSpecificLocale) {
|
||||
versionSnapshotResult = await beforeChange({
|
||||
...beforeChangeArgs,
|
||||
docWithLocales,
|
||||
})
|
||||
|
||||
const lastPublished = await getLatestCollectionVersion({
|
||||
id,
|
||||
config: collectionConfig,
|
||||
payload,
|
||||
published: true,
|
||||
query: {
|
||||
collection: collectionConfig.slug,
|
||||
locale,
|
||||
req,
|
||||
where: combineQueries({ id: { equals: id } }, accessResults),
|
||||
},
|
||||
req,
|
||||
})
|
||||
|
||||
publishedDocWithLocales = lastPublished ? lastPublished : {}
|
||||
}
|
||||
|
||||
let result = await beforeChange({
|
||||
...beforeChangeArgs,
|
||||
docWithLocales: publishedDocWithLocales,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Handle potential password update
|
||||
// /////////////////////////////////////
|
||||
|
||||
const dataToUpdate: Record<string, unknown> = { ...result }
|
||||
|
||||
if (shouldSavePassword && typeof password === 'string') {
|
||||
const { hash, salt } = await generatePasswordSaltHash({
|
||||
collection: collectionConfig,
|
||||
password,
|
||||
req,
|
||||
})
|
||||
dataToUpdate.salt = salt
|
||||
dataToUpdate.hash = hash
|
||||
delete dataToUpdate.password
|
||||
delete data.password
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Update
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (!shouldSaveDraft) {
|
||||
result = await req.payload.db.updateOne({
|
||||
id,
|
||||
collection: collectionConfig.slug,
|
||||
data: dataToUpdate,
|
||||
locale,
|
||||
req,
|
||||
select,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Create version
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
result = await saveVersion({
|
||||
id,
|
||||
autosave,
|
||||
collection: collectionConfig,
|
||||
docWithLocales: result,
|
||||
draft: shouldSaveDraft,
|
||||
payload,
|
||||
publishSpecificLocale,
|
||||
req,
|
||||
select,
|
||||
snapshot: versionSnapshotResult,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result,
|
||||
draft: draftArg,
|
||||
fallbackLocale,
|
||||
global: null,
|
||||
locale,
|
||||
overrideAccess,
|
||||
populate,
|
||||
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())
|
||||
|
||||
return result as TransformCollectionWithSelect<TSlug, TSelect>
|
||||
}
|
||||
Reference in New Issue
Block a user