Compare commits
55 Commits
feat/local
...
build/part
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18ac47c1fc | ||
|
|
e6f98d1ba9 | ||
|
|
fb148fff0f | ||
|
|
aa0b6ebf61 | ||
|
|
549b8c8e87 | ||
|
|
f5ea7b47cf | ||
|
|
05b9d94cd2 | ||
|
|
d4039f2f9e | ||
|
|
7a392ddbff | ||
|
|
d55b6a3db9 | ||
|
|
9043b10792 | ||
|
|
ecf05725e6 | ||
|
|
918bd72335 | ||
|
|
4629784c99 | ||
|
|
a304dc4b01 | ||
|
|
8ab05b0c22 | ||
|
|
085c1d0cac | ||
|
|
61117ee5cb | ||
|
|
05b03b2dcd | ||
|
|
120735c55c | ||
|
|
16ad7a671f | ||
|
|
31ae27b67d | ||
|
|
8217842bb3 | ||
|
|
2e09da8a8c | ||
|
|
5d6c29f3df | ||
|
|
df4af70fb9 | ||
|
|
90e1843795 | ||
|
|
5ee36fced3 | ||
|
|
f306785eb2 | ||
|
|
6a6ef8f786 | ||
|
|
a865a902d5 | ||
|
|
878763b36d | ||
|
|
3c29015887 | ||
|
|
9631060383 | ||
|
|
5cfb1daaae | ||
|
|
9278eec2b6 | ||
|
|
a3ef5eee7b | ||
|
|
f95d6ba94a | ||
|
|
6ada450531 | ||
|
|
9004205b84 | ||
|
|
6757f7d459 | ||
|
|
2ae26d33e3 | ||
|
|
5043a8a43f | ||
|
|
6848cf43ed | ||
|
|
2e0595b170 | ||
|
|
43b40f0b00 | ||
|
|
c9584a932a | ||
|
|
69fac593ca | ||
|
|
415fbf1341 | ||
|
|
cc13ae77fb | ||
|
|
afcc970e36 | ||
|
|
6b051bd59e | ||
|
|
082c4f0d71 | ||
|
|
0252681313 | ||
|
|
690e99f2f9 |
24
.github/workflows/dispatch-event.yml
vendored
Normal file
24
.github/workflows/dispatch-event.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: dispatch-event
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
PAYLOAD_PUSH_MAIN_EVENT: payload-push-main-event
|
||||
|
||||
jobs:
|
||||
repository-dispatch:
|
||||
name: Repository dispatch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Dispatch event
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.PAYLOAD_REPOSITORY_DISPATCH }}
|
||||
repository: ${{ secrets.REMOTE_REPOSITORY }}
|
||||
event-type: ${{ env.PAYLOAD_PUSH_MAIN_EVENT }}
|
||||
client-payload: '{"event": {"head_commit": {"id": "${{ env.GITHUB_SHA }}"}}}' # mocked for testing
|
||||
# client-payload: '{"event": ${{ toJson(github.event) }}}'
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -317,3 +317,4 @@ test/databaseAdapter.js
|
||||
/filename-compound-index
|
||||
/media-with-relation-preview
|
||||
/media-without-relation-preview
|
||||
/media-without-cache-tags
|
||||
|
||||
@@ -59,16 +59,17 @@ To accomplish this, Payload exposes the [Access Operation](../authentication/ope
|
||||
|
||||
If you use `id` or `data` within your access control functions, make sure to check that they are defined first. If they are not, then you can assume that your Access Control is being executed via the Access Operation to determine solely what the user can do within the Admin Panel.
|
||||
|
||||
|
||||
## Locale Specific Access Control
|
||||
|
||||
To implement locale-specific access control, you can use the `locale` argument in your access control functions. This argument allows you to evaluate the current locale of the request and determine access permissions accordingly.
|
||||
To implement locale-specific access control, you can use the `req.locale` argument in your access control functions. This argument allows you to evaluate the current locale of the request and determine access permissions accordingly.
|
||||
|
||||
Here is an example of how you can implement localized access control:
|
||||
Here is an example:
|
||||
|
||||
```ts
|
||||
const access = ({ locale }) => {
|
||||
const access = ({ req }) => {
|
||||
// Grant access if the locale is 'en'
|
||||
if (locale === 'en') {
|
||||
if (req.locale === 'en') {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -397,12 +397,17 @@ export const CustomArrayManager = () => {
|
||||
onClick={() => {
|
||||
addFieldRow({
|
||||
path: "arrayField",
|
||||
rowIndex: 0,
|
||||
data: {
|
||||
textField: "text",
|
||||
// blockType: "yourBlockSlug",
|
||||
// ^ if managing a block array, you need to specify the block type
|
||||
schemaPath: "arrayField",
|
||||
rowIndex: 0, // optionally specify the index to add the row at
|
||||
subFieldState: {
|
||||
textField: {
|
||||
initialValue: 'New row text',
|
||||
valid: true,
|
||||
value: 'New row text',
|
||||
},
|
||||
},
|
||||
// blockType: "yourBlockSlug",
|
||||
// ^ if managing a block array, you need to specify the block type
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -595,12 +600,17 @@ export const CustomArrayManager = () => {
|
||||
onClick={() => {
|
||||
replaceFieldRow({
|
||||
path: "arrayField",
|
||||
rowIndex: 0,
|
||||
data: {
|
||||
textField: "updated text",
|
||||
// blockType: "yourBlockSlug",
|
||||
// ^ if managing a block array, you need to specify the block type
|
||||
schemaPath: "arrayField",
|
||||
rowIndex: 0, // optionally specify the index to add the row at
|
||||
subFieldState: {
|
||||
textField: {
|
||||
initialValue: 'Updated text',
|
||||
valid: true,
|
||||
value: 'Upddated text',
|
||||
},
|
||||
},
|
||||
// blockType: "yourBlockSlug",
|
||||
// ^ if managing a block array, you need to specify the block type
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -52,7 +52,7 @@ export default buildConfig({
|
||||
|
||||
<Banner type="info">
|
||||
**Note:**
|
||||
If when using `vercelPostgresAdapter` your `process.env.POSTGRES_URL` or `pool.connectionString` points to a local database (e.g hostname has `localhost` or `127.0.0.1`) we use the `pg` module for pooling instead of `@vercel/postgres`. This is because `@vercel/postgres` doesn't work with local databases, if you want to disable that behavior, you can pass `forceUseVercelPostgres: true` to adapter's 'args and follow [Vercel guide](https://vercel.com/docs/storage/vercel-postgres/local-development#option-2:-local-postgres-instance-with-docker) for a Docker Neon DB setup.
|
||||
If you're using `vercelPostgresAdapter` your `process.env.POSTGRES_URL` or `pool.connectionString` points to a local database (e.g hostname has `localhost` or `127.0.0.1`) we use the `pg` module for pooling instead of `@vercel/postgres`. This is because `@vercel/postgres` doesn't work with local databases, if you want to disable that behavior, you can pass `forceUseVercelPostgres: true` to adapter's 'args and follow [Vercel guide](https://vercel.com/docs/storage/vercel-postgres/local-development#option-2:-local-postgres-instance-with-docker) for a Docker Neon DB setup.
|
||||
</Banner>
|
||||
|
||||
## Options
|
||||
|
||||
@@ -305,6 +305,8 @@ The Rich Text Field editor configuration has an `admin` property with the follow
|
||||
| --- | --- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
||||
| **`hideInsertParagraphAtEnd`** | Set this property to `true` to hide the "+" button that appears at the end of the editor |
|
||||
|
||||
|
||||
### Disable the gutter
|
||||
|
||||
@@ -336,4 +338,4 @@ You can customize the placeholder (the text that appears in the editor when it's
|
||||
},
|
||||
}),
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -92,6 +92,7 @@ _An asterisk denotes that an option is required._
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`bulkUpload`** | Allow users to upload in bulk from the list view, default is true |
|
||||
| **`cacheTags`** | Set to `false` to disable the cache tag set in the UI for the admin thumbnail component. Useful for when CDNs don't allow certain cache queries. |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -46,7 +46,7 @@
|
||||
"build:storage-s3": "turbo build --filter \"@payloadcms/storage-s3\"",
|
||||
"build:storage-uploadthing": "turbo build --filter \"@payloadcms/storage-uploadthing\"",
|
||||
"build:storage-vercel-blob": "turbo build --filter \"@payloadcms/storage-vercel-blob\"",
|
||||
"build:tests": "pnpm --filter payload-test-suite run typecheck",
|
||||
"build:tests": "pnpm --filter payload-test-suite run build",
|
||||
"build:translations": "turbo build --filter \"@payloadcms/translations\"",
|
||||
"build:ui": "turbo build --filter \"@payloadcms/ui\"",
|
||||
"clean": "turbo clean",
|
||||
@@ -150,7 +150,7 @@
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"lint-staged": "15.2.7",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"mongodb-memory-server": "^10",
|
||||
"next": "15.1.3",
|
||||
"open": "^10.1.0",
|
||||
"p-limit": "^5.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -78,6 +78,8 @@ export async function manageEnvFiles(args: {
|
||||
}): Promise<void> {
|
||||
const { cliArgs, databaseType, databaseUri, payloadSecret, projectDir, template } = args
|
||||
|
||||
const debugFlag = cliArgs['--debug']
|
||||
|
||||
if (cliArgs['--dry-run']) {
|
||||
debug(`DRY RUN: Environment files managed`)
|
||||
return
|
||||
@@ -100,7 +102,10 @@ export async function manageEnvFiles(args: {
|
||||
updatedExampleContents = updateEnvExampleVariables(envExampleContents, databaseType)
|
||||
|
||||
await fs.writeFile(envExamplePath, updatedExampleContents.trimEnd() + '\n')
|
||||
debug(`.env.example file successfully updated`)
|
||||
|
||||
if (debugFlag) {
|
||||
debug(`.env.example file successfully updated`)
|
||||
}
|
||||
} else {
|
||||
updatedExampleContents = `# Added by Payload\nDATABASE_URI=your-connection-string-here\nPAYLOAD_SECRET=YOUR_SECRET_HERE\n`
|
||||
await fs.writeFile(envExamplePath, updatedExampleContents.trimEnd() + '\n')
|
||||
@@ -116,7 +121,9 @@ export async function manageEnvFiles(args: {
|
||||
)
|
||||
await fs.writeFile(envPath, `# Added by Payload\n${envContent.trimEnd()}\n`)
|
||||
|
||||
debug(`.env file successfully created or updated`)
|
||||
if (debugFlag) {
|
||||
debug(`.env file successfully created or updated`)
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
error('Unable to manage environment files')
|
||||
if (err instanceof Error) {
|
||||
|
||||
@@ -34,7 +34,7 @@ export function helpMessage(): void {
|
||||
|
||||
-n {underline my-payload-app} Set project name
|
||||
-t {underline template_name} Choose specific template
|
||||
-e {underline example_name} Choose specific exmaple
|
||||
-e {underline example_name} Choose specific example
|
||||
|
||||
{dim Available templates: ${formatTemplates(validTemplates)}}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
/* TODO: remove the following lines */
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -57,7 +57,7 @@
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/mongoose-aggregate-paginate-v2": "1.0.12",
|
||||
"mongodb": "6.10.0",
|
||||
"mongodb-memory-server": "^9",
|
||||
"mongodb-memory-server": "^10",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { PayloadRequest } from 'payload'
|
||||
|
||||
import httpStatus from 'http-status'
|
||||
import { APIError, ValidationError } from 'payload'
|
||||
import { ValidationError } from 'payload'
|
||||
|
||||
export const handleError = ({
|
||||
collection,
|
||||
@@ -10,7 +9,7 @@ export const handleError = ({
|
||||
req,
|
||||
}: {
|
||||
collection?: string
|
||||
error: unknown
|
||||
error: Error
|
||||
global?: string
|
||||
req?: Partial<PayloadRequest>
|
||||
}) => {
|
||||
@@ -18,10 +17,9 @@ export const handleError = ({
|
||||
throw error
|
||||
}
|
||||
|
||||
const message = req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique'
|
||||
|
||||
// Handle uniqueness error from MongoDB
|
||||
if ('code' in error && error.code === 11000 && 'keyValue' in error && error.keyValue) {
|
||||
const message = req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique'
|
||||
throw new ValidationError(
|
||||
{
|
||||
collection,
|
||||
@@ -37,5 +35,5 @@ export const handleError = ({
|
||||
)
|
||||
}
|
||||
|
||||
throw new APIError(message, httpStatus.BAD_REQUEST)
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }, { "path": "../translations" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../payload"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../payload"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../payload"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }, { "path": "../translations" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }] // db-mongodb depends on payload
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -51,7 +51,7 @@ export function getRouteInfo({
|
||||
globalConfig = config.globals.find((global) => global.slug === globalSlug)
|
||||
}
|
||||
|
||||
// If the collection is using a custom ID, we need to determine it's type
|
||||
// If the collection is using a custom ID, we need to determine its type
|
||||
if (collectionConfig && payload) {
|
||||
if (payload.collections?.[collectionSlug]?.customIDType) {
|
||||
idType = payload.collections?.[collectionSlug].customIDType
|
||||
|
||||
@@ -7,7 +7,6 @@ import * as qs from 'qs-esm'
|
||||
|
||||
import type { Args } from './types.js'
|
||||
|
||||
import { getRequestLocale } from '../getRequestLocale.js'
|
||||
import { initReq } from '../initReq.js'
|
||||
import { getRouteInfo } from './handleAdminPage.js'
|
||||
import { handleAuthRedirect } from './handleAuthRedirect.js'
|
||||
@@ -32,7 +31,7 @@ export const initPage = async ({
|
||||
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const { permissions, req } = await initReq(payload.config, {
|
||||
const { locale, permissions, req } = await initReq(payload.config, {
|
||||
fallbackLocale: false,
|
||||
req: {
|
||||
headers,
|
||||
@@ -58,12 +57,6 @@ export const initPage = async ({
|
||||
[],
|
||||
)
|
||||
|
||||
const locale = await getRequestLocale({
|
||||
req,
|
||||
})
|
||||
|
||||
req.locale = locale?.code
|
||||
|
||||
const visibleEntities: VisibleEntities = {
|
||||
collections: collections
|
||||
.map(({ slug, admin: { hidden } }) =>
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import type { I18n, I18nClient } from '@payloadcms/translations'
|
||||
import type { PayloadRequest, SanitizedConfig, SanitizedPermissions } from 'payload'
|
||||
import type { Locale, PayloadRequest, SanitizedConfig, SanitizedPermissions } from 'payload'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { createLocalReq, getPayload, getRequestLanguage, parseCookies } from 'payload'
|
||||
import {
|
||||
createLocalReq,
|
||||
executeAuthStrategies,
|
||||
getAccessResults,
|
||||
getPayload,
|
||||
getRequestLanguage,
|
||||
parseCookies,
|
||||
} from 'payload'
|
||||
import { cache } from 'react'
|
||||
|
||||
import { getRequestLocale } from './getRequestLocale.js'
|
||||
|
||||
type Result = {
|
||||
locale?: Locale
|
||||
permissions: SanitizedPermissions
|
||||
req: PayloadRequest
|
||||
}
|
||||
@@ -33,7 +43,14 @@ export const initReq = cache(async function (
|
||||
language: languageCode,
|
||||
})
|
||||
|
||||
const { permissions, user } = await payload.auth({ headers })
|
||||
/**
|
||||
* Cannot simply call `payload.auth` here, as we need the user to get the locale, and we need the locale to get the access results
|
||||
* I.e. the `payload.auth` function would call `getAccessResults` without a fully-formed `req` object
|
||||
*/
|
||||
const { responseHeaders, user } = await executeAuthStrategies({
|
||||
headers,
|
||||
payload,
|
||||
})
|
||||
|
||||
const { req: reqOverrides, ...optionsOverrides } = overrides || {}
|
||||
|
||||
@@ -43,6 +60,7 @@ export const initReq = cache(async function (
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
i18n: i18n as I18n,
|
||||
responseHeaders,
|
||||
url: `${payload.config.serverURL}`,
|
||||
user,
|
||||
...(reqOverrides || {}),
|
||||
@@ -52,7 +70,18 @@ export const initReq = cache(async function (
|
||||
payload,
|
||||
)
|
||||
|
||||
const locale = await getRequestLocale({
|
||||
req,
|
||||
})
|
||||
|
||||
req.locale = locale?.code
|
||||
|
||||
const permissions = await getAccessResults({
|
||||
req,
|
||||
})
|
||||
|
||||
return {
|
||||
locale,
|
||||
permissions,
|
||||
req,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../payload" },
|
||||
{ "path": "../ui" },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload-cloud",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -7,7 +7,7 @@ type NodemailerAdapter = ReturnType<typeof nodemailerAdapter>
|
||||
|
||||
export const payloadCloudEmail = async (
|
||||
args: PayloadCloudEmailOptions,
|
||||
): Promise<NodemailerAdapter> | undefined => {
|
||||
): Promise<NodemailerAdapter | undefined> => {
|
||||
if (process.env.PAYLOAD_CLOUD !== 'true' || !args) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ export const getAfterDeleteHook = ({
|
||||
const { identityID, storageClient } = await getStorageClient()
|
||||
|
||||
const filesToDelete: string[] = [
|
||||
doc.filename,
|
||||
...Object.values(doc?.sizes || []).map((resizedFileData) => resizedFileData?.filename),
|
||||
doc.filename || '',
|
||||
...Object.values(doc?.sizes || [])
|
||||
.map((resizedFileData) => resizedFileData.filename)
|
||||
.filter((filename): filename is string => filename !== null),
|
||||
]
|
||||
|
||||
const promises = filesToDelete.map(async (filename) => {
|
||||
|
||||
@@ -128,7 +128,7 @@ export const payloadCloudPlugin =
|
||||
return config
|
||||
}
|
||||
|
||||
const oldAutoRunCopy = config.jobs.autoRun
|
||||
const oldAutoRunCopy = config.jobs.autoRun ?? []
|
||||
|
||||
const newAutoRun = async (payload: Payload) => {
|
||||
const instance = generateRandomString()
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Args {
|
||||
}
|
||||
|
||||
// Convert a stream into a promise that resolves with a Buffer
|
||||
const streamToBuffer = async (readableStream) => {
|
||||
const streamToBuffer = async (readableStream: any) => {
|
||||
const chunks = []
|
||||
for await (const chunk of readableStream) {
|
||||
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk)
|
||||
@@ -52,7 +52,7 @@ export const getStaticHandler = ({ cachingOptions, collection }: Args): StaticHa
|
||||
Key: key,
|
||||
})
|
||||
|
||||
if (!object.Body) {
|
||||
if (!object.Body || !object.ContentType || !object.ETag) {
|
||||
return new Response(null, { status: 404, statusText: 'Not Found' })
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,13 @@ export const authAsCognitoUser = async (
|
||||
return sessionAndToken
|
||||
}
|
||||
|
||||
if (!process.env.PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID) {
|
||||
throw new Error('PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID is required')
|
||||
}
|
||||
if (!process.env.PAYLOAD_CLOUD_COGNITO_USER_POOL_ID) {
|
||||
throw new Error('PAYLOAD_CLOUD_COGNITO_USER_POOL_ID is required')
|
||||
}
|
||||
|
||||
const userPool = new CognitoUserPool({
|
||||
ClientId: process.env.PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID,
|
||||
UserPoolId: process.env.PAYLOAD_CLOUD_COGNITO_USER_POOL_ID,
|
||||
|
||||
@@ -23,6 +23,16 @@ export const getStorageClient: GetStorageClient = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!process.env.PAYLOAD_CLOUD_PROJECT_ID) {
|
||||
throw new Error('PAYLOAD_CLOUD_PROJECT_ID is required')
|
||||
}
|
||||
if (!process.env.PAYLOAD_CLOUD_COGNITO_PASSWORD) {
|
||||
throw new Error('PAYLOAD_CLOUD_COGNITO_PASSWORD is required')
|
||||
}
|
||||
if (!process.env.PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID) {
|
||||
throw new Error('PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID is required')
|
||||
}
|
||||
|
||||
session = await authAsCognitoUser(
|
||||
process.env.PAYLOAD_CLOUD_PROJECT_ID,
|
||||
process.env.PAYLOAD_CLOUD_COGNITO_PASSWORD,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
export type ClientFieldWithOptionalType = MarkOptional<ClientField, 'type'>
|
||||
|
||||
export type ClientComponentProps = {
|
||||
customComponents: FormField['customComponents']
|
||||
customComponents?: FormField['customComponents']
|
||||
field: ClientBlock | ClientField | ClientTab
|
||||
forceRender?: boolean
|
||||
permissions?: SanitizedFieldPermissions
|
||||
|
||||
@@ -45,14 +45,13 @@ export type FieldState = {
|
||||
*/
|
||||
fieldSchema?: Field
|
||||
filterOptions?: FilterOptionsResult
|
||||
initialValue: unknown
|
||||
isSidebar?: boolean
|
||||
initialValue?: unknown
|
||||
passesCondition?: boolean
|
||||
requiresRender?: boolean
|
||||
rows?: Row[]
|
||||
valid: boolean
|
||||
valid?: boolean
|
||||
validate?: Validate
|
||||
value: unknown
|
||||
value?: unknown
|
||||
}
|
||||
|
||||
export type FieldStateWithoutComponents = Omit<FieldState, 'customComponents'>
|
||||
|
||||
@@ -453,6 +453,12 @@ export type RenderedField = {
|
||||
Field: React.ReactNode
|
||||
indexPath?: string
|
||||
initialSchemaPath?: string
|
||||
/**
|
||||
* @deprecated
|
||||
* This is a legacy property that will be removed in v4.
|
||||
* Please use `fieldIsSidebar(field)` from `payload` instead.
|
||||
* Or check `field.admin.position === 'sidebar'` directly.
|
||||
*/
|
||||
isSidebar: boolean
|
||||
path: string
|
||||
schemaPath: string
|
||||
|
||||
@@ -13,7 +13,6 @@ export const accessHandler: PayloadHandler = async (req) => {
|
||||
|
||||
try {
|
||||
const results = await accessOperation({
|
||||
locale: req.locale,
|
||||
req,
|
||||
})
|
||||
|
||||
|
||||
@@ -8,11 +8,10 @@ type OperationArgs = {
|
||||
disableErrors?: boolean
|
||||
id?: number | string
|
||||
isReadingStaticFile?: boolean
|
||||
locale?: string
|
||||
req: PayloadRequest
|
||||
}
|
||||
const executeAccess = async (
|
||||
{ id, data, disableErrors, isReadingStaticFile = false, locale, req }: OperationArgs,
|
||||
{ id, data, disableErrors, isReadingStaticFile = false, req }: OperationArgs,
|
||||
access: Access,
|
||||
): Promise<AccessResult> => {
|
||||
if (access) {
|
||||
@@ -20,7 +19,6 @@ const executeAccess = async (
|
||||
id,
|
||||
data,
|
||||
isReadingStaticFile,
|
||||
locale,
|
||||
req,
|
||||
})
|
||||
|
||||
|
||||
@@ -5,11 +5,9 @@ import { getEntityPolicies } from '../utilities/getEntityPolicies.js'
|
||||
import { sanitizePermissions } from '../utilities/sanitizePermissions.js'
|
||||
|
||||
type GetAccessResultsArgs = {
|
||||
locale?: string
|
||||
req: PayloadRequest
|
||||
}
|
||||
export async function getAccessResults({
|
||||
locale,
|
||||
req,
|
||||
}: GetAccessResultsArgs): Promise<SanitizedPermissions> {
|
||||
const results = {} as Permissions
|
||||
@@ -48,7 +46,6 @@ export async function getAccessResults({
|
||||
const collectionPolicy = await getEntityPolicies({
|
||||
type: 'collection',
|
||||
entity: collection,
|
||||
locale,
|
||||
operations: collectionOperations,
|
||||
req,
|
||||
})
|
||||
@@ -70,7 +67,6 @@ export async function getAccessResults({
|
||||
const globalPolicy = await getEntityPolicies({
|
||||
type: 'global',
|
||||
entity: global,
|
||||
locale,
|
||||
operations: globalOperations,
|
||||
req,
|
||||
})
|
||||
|
||||
@@ -6,17 +6,16 @@ import { adminInit as adminInitTelemetry } from '../../utilities/telemetry/event
|
||||
import { getAccessResults } from '../getAccessResults.js'
|
||||
|
||||
type Arguments = {
|
||||
locale?: string
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
export const accessOperation = async (args: Arguments): Promise<SanitizedPermissions> => {
|
||||
const { locale, req } = args
|
||||
const { req } = args
|
||||
|
||||
adminInitTelemetry(req)
|
||||
|
||||
try {
|
||||
return getAccessResults({ locale, req })
|
||||
return getAccessResults({ req })
|
||||
} catch (e: unknown) {
|
||||
await killTransaction(req)
|
||||
throw e
|
||||
|
||||
@@ -63,7 +63,7 @@ export const unlockOperation = async <TSlug extends CollectionSlug>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (!overrideAccess) {
|
||||
await executeAccess({ locale, req }, collectionConfig.access.unlock)
|
||||
await executeAccess({ req }, collectionConfig.access.unlock)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
import { Cron } from 'croner'
|
||||
import minimist from 'minimist'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
@@ -11,7 +12,18 @@ import { generateImportMap } from './generateImportMap/index.js'
|
||||
import { generateTypes } from './generateTypes.js'
|
||||
import { info } from './info.js'
|
||||
import { loadEnv } from './loadEnv.js'
|
||||
import { migrate } from './migrate.js'
|
||||
import { migrate, availableCommands as migrateCommands } from './migrate.js'
|
||||
|
||||
// Note: this does not account for any user bin scripts
|
||||
const availableScripts = [
|
||||
'generate:db-schema',
|
||||
'generate:importmap',
|
||||
'generate:types',
|
||||
'info',
|
||||
'jobs:run',
|
||||
'run',
|
||||
...migrateCommands,
|
||||
] as const
|
||||
|
||||
export const bin = async () => {
|
||||
loadEnv()
|
||||
@@ -137,6 +149,8 @@ export const bin = async () => {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
console.error(`Unknown script: "${script}".`)
|
||||
console.error(script ? `Unknown command: "${script}"` : 'Please provide a command to run')
|
||||
console.log(`\nAvailable commands:\n${availableScripts.map((c) => ` - ${c}`).join('\n')}`)
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const prettySyncLogger = {
|
||||
loggerOptions: {},
|
||||
}
|
||||
|
||||
const availableCommands = [
|
||||
export const availableCommands = [
|
||||
'migrate',
|
||||
'migrate:create',
|
||||
'migrate:down',
|
||||
|
||||
@@ -152,6 +152,7 @@ export const sanitizeCollection = async (
|
||||
// sanitize fields for reserved names
|
||||
sanitizeUploadFields(sanitized.fields, sanitized)
|
||||
|
||||
sanitized.upload.cacheTags = sanitized.upload?.cacheTags ?? true
|
||||
sanitized.upload.bulkUpload = sanitized.upload?.bulkUpload ?? true
|
||||
sanitized.upload.staticDir = sanitized.upload.staticDir || sanitized.slug
|
||||
sanitized.admin.useAsTitle =
|
||||
|
||||
@@ -11,6 +11,7 @@ import { duplicateHandler } from './duplicate.js'
|
||||
import { findHandler } from './find.js'
|
||||
import { findByIDHandler } from './findByID.js'
|
||||
import { findVersionByIDHandler } from './findVersionByID.js'
|
||||
import { findVersionsHandler } from './findVersions.js'
|
||||
import { getFileHandler } from './getFile.js'
|
||||
import { previewHandler } from './preview.js'
|
||||
import { restoreVersionHandler } from './restoreVersion.js'
|
||||
@@ -45,6 +46,11 @@ export const defaultCollectionEndpoints: Endpoint[] = [
|
||||
method: 'post',
|
||||
path: '/access/:id?',
|
||||
},
|
||||
{
|
||||
handler: findVersionsHandler,
|
||||
method: 'get',
|
||||
path: '/versions',
|
||||
},
|
||||
{
|
||||
handler: duplicateHandler,
|
||||
method: 'post',
|
||||
|
||||
@@ -141,7 +141,7 @@ export const createOperation = async <
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (!overrideAccess) {
|
||||
await executeAccess({ data, locale, req }, collectionConfig.access.create)
|
||||
await executeAccess({ data, req }, collectionConfig.access.create)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -23,6 +23,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
import { deleteCollectionVersions } from '../../versions/deleteCollectionVersions.js'
|
||||
import { deleteScheduledPublishJobs } from '../../versions/deleteScheduledPublishJobs.js'
|
||||
import { buildAfterOperation } from './utils.js'
|
||||
|
||||
export type Arguments = {
|
||||
@@ -97,7 +98,7 @@ export const deleteOperation = async <
|
||||
let accessResult: AccessResult
|
||||
|
||||
if (!overrideAccess) {
|
||||
accessResult = await executeAccess({ locale, req }, collectionConfig.access.delete)
|
||||
accessResult = await executeAccess({ req }, collectionConfig.access.delete)
|
||||
}
|
||||
|
||||
await validateQueryPaths({
|
||||
@@ -177,6 +178,18 @@ export const deleteOperation = async <
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete scheduled posts
|
||||
// /////////////////////////////////////
|
||||
if (collectionConfig.versions?.drafts && collectionConfig.versions.drafts.schedulePublish) {
|
||||
await deleteScheduledPublishJobs({
|
||||
id,
|
||||
slug: collectionConfig.slug,
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete document
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -19,6 +19,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
import { deleteCollectionVersions } from '../../versions/deleteCollectionVersions.js'
|
||||
import { deleteScheduledPublishJobs } from '../../versions/deleteScheduledPublishJobs.js'
|
||||
import { buildAfterOperation } from './utils.js'
|
||||
|
||||
export type Arguments = {
|
||||
@@ -85,7 +86,7 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
// /////////////////////////////////////
|
||||
|
||||
const accessResults = !overrideAccess
|
||||
? await executeAccess({ id, locale, req }, collectionConfig.access.delete)
|
||||
? await executeAccess({ id, req }, collectionConfig.access.delete)
|
||||
: true
|
||||
const hasWhereAccess = hasWhereAccessResult(accessResults)
|
||||
|
||||
@@ -155,6 +156,18 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete scheduled posts
|
||||
// /////////////////////////////////////
|
||||
if (collectionConfig.versions?.drafts && collectionConfig.versions.drafts.schedulePublish) {
|
||||
await deleteScheduledPublishJobs({
|
||||
id,
|
||||
slug: collectionConfig.slug,
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete document
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -102,10 +102,7 @@ export const findOperation = async <
|
||||
let accessResult: AccessResult
|
||||
|
||||
if (!overrideAccess) {
|
||||
accessResult = await executeAccess(
|
||||
{ disableErrors, locale, req },
|
||||
collectionConfig.access.read,
|
||||
)
|
||||
accessResult = await executeAccess({ disableErrors, req }, collectionConfig.access.read)
|
||||
|
||||
// If errors are disabled, and access returns false, return empty results
|
||||
if (accessResult === false) {
|
||||
|
||||
@@ -88,7 +88,7 @@ export const findByIDOperation = async <
|
||||
// /////////////////////////////////////
|
||||
|
||||
const accessResult = !overrideAccess
|
||||
? await executeAccess({ id, disableErrors, locale, req }, collectionConfig.access.read)
|
||||
? await executeAccess({ id, disableErrors, req }, collectionConfig.access.read)
|
||||
: true
|
||||
|
||||
// If errors are disabled, and access returns false, return null
|
||||
|
||||
@@ -50,10 +50,7 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
const accessResults = !overrideAccess
|
||||
? await executeAccess(
|
||||
{ id, disableErrors, locale, req },
|
||||
collectionConfig.access.readVersions,
|
||||
)
|
||||
? await executeAccess({ id, disableErrors, req }, collectionConfig.access.readVersions)
|
||||
: true
|
||||
|
||||
// If errors are disabled, and access returns false, return null
|
||||
|
||||
@@ -53,7 +53,7 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
|
||||
let accessResults
|
||||
|
||||
if (!overrideAccess) {
|
||||
accessResults = await executeAccess({ locale, req }, collectionConfig.access.readVersions)
|
||||
accessResults = await executeAccess({ req }, collectionConfig.access.readVersions)
|
||||
}
|
||||
|
||||
const versionFields = buildVersionCollectionFields(payload.config, collectionConfig, true)
|
||||
|
||||
@@ -74,7 +74,7 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
const accessResults = !overrideAccess
|
||||
? await executeAccess({ id: parentDocID, locale, req }, collectionConfig.access.update)
|
||||
? await executeAccess({ id: parentDocID, req }, collectionConfig.access.update)
|
||||
: true
|
||||
const hasWherePolicy = hasWhereAccessResult(accessResults)
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -116,7 +110,7 @@ export const updateOperation = async <
|
||||
|
||||
let accessResult: AccessResult
|
||||
if (!overrideAccess) {
|
||||
accessResult = await executeAccess({ locale, req }, collectionConfig.access.update)
|
||||
accessResult = await executeAccess({ req }, collectionConfig.access.update)
|
||||
}
|
||||
|
||||
await validateQueryPaths({
|
||||
@@ -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,17 +107,14 @@ 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
|
||||
// /////////////////////////////////////
|
||||
|
||||
const accessResults = !overrideAccess
|
||||
? await executeAccess({ id, data, locale, req }, collectionConfig.access.update)
|
||||
? await executeAccess({ id, data, req }, collectionConfig.access.update)
|
||||
: true
|
||||
const hasWherePolicy = hasWhereAccessResult(accessResults)
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -326,8 +326,6 @@ export type AccessArgs<TData = any> = {
|
||||
id?: number | string
|
||||
/** If true, the request is for a static file */
|
||||
isReadingStaticFile?: boolean
|
||||
/** The locale of the request */
|
||||
locale?: string
|
||||
/** The original request that requires an access check */
|
||||
req: PayloadRequest
|
||||
}
|
||||
@@ -793,7 +791,7 @@ export type Config = {
|
||||
dependencies?: AdminDependencies
|
||||
/**
|
||||
* @deprecated
|
||||
* This option is deprecated and will be removed in the next major version.
|
||||
* This option is deprecated and will be removed in v4.
|
||||
* To disable the admin panel itself, delete your `/app/(payload)/admin` directory.
|
||||
* To disable all REST API and GraphQL endpoints, delete your `/app/(payload)/api` directory.
|
||||
* Note: If you've modified the default paths via `admin.routes`, delete those directories instead.
|
||||
@@ -805,7 +803,6 @@ export type Config = {
|
||||
* @default true
|
||||
*/
|
||||
autoGenerate?: boolean
|
||||
|
||||
/** The base directory for component paths starting with /.
|
||||
*
|
||||
* By default, this is process.cwd()
|
||||
|
||||
@@ -216,8 +216,6 @@ export type FieldAccess<TData extends TypeWithID = any, TSiblingData = any> = (a
|
||||
* The `id` of the current document being read or updated. `id` is undefined during the `create` operation.
|
||||
*/
|
||||
id?: number | string
|
||||
/** The locale of the request */
|
||||
locale?: string
|
||||
/** The `payload` object to interface with the payload API */
|
||||
req: PayloadRequest
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,7 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
|
||||
let accessResult: AccessResult
|
||||
|
||||
if (!overrideAccess) {
|
||||
accessResult = await executeAccess({ locale, req }, globalConfig.access.read)
|
||||
accessResult = await executeAccess({ req }, globalConfig.access.read)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -46,7 +46,7 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
|
||||
// /////////////////////////////////////
|
||||
|
||||
const accessResults = !overrideAccess
|
||||
? await executeAccess({ id, disableErrors, locale, req }, globalConfig.access.readVersions)
|
||||
? await executeAccess({ id, disableErrors, req }, globalConfig.access.readVersions)
|
||||
: true
|
||||
|
||||
// If errors are disabled, and access returns false, return null
|
||||
|
||||
@@ -53,7 +53,7 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
const accessResults = !overrideAccess
|
||||
? await executeAccess({ locale, req }, globalConfig.access.readVersions)
|
||||
? await executeAccess({ req }, globalConfig.access.readVersions)
|
||||
: true
|
||||
|
||||
await validateQueryPaths({
|
||||
|
||||
@@ -44,7 +44,7 @@ export const restoreVersionOperation = async <T extends TypeWithVersion<T> = any
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (!overrideAccess) {
|
||||
await executeAccess({ locale, req }, globalConfig.access.update)
|
||||
await executeAccess({ req }, globalConfig.access.update)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -88,7 +88,6 @@ export const updateOperation = async <
|
||||
? await executeAccess(
|
||||
{
|
||||
data,
|
||||
locale,
|
||||
req,
|
||||
},
|
||||
globalConfig.access.update,
|
||||
|
||||
@@ -1112,6 +1112,7 @@ export type {
|
||||
UpdateVersion,
|
||||
UpdateVersionArgs,
|
||||
Upsert,
|
||||
UpsertArgs,
|
||||
} from './database/types.js'
|
||||
export type { EmailAdapter as PayloadEmailAdapter, SendEmailOptions } from './email/types.js'
|
||||
export {
|
||||
|
||||
@@ -100,6 +100,12 @@ export type UploadConfig = {
|
||||
* @default true
|
||||
*/
|
||||
bulkUpload?: boolean
|
||||
/**
|
||||
* Appends a cache tag to the image URL when fetching the thumbnail in the admin panel. It may be desirable to disable this when hosting via CDNs with strict parameters.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
cacheTags?: boolean
|
||||
/**
|
||||
* Enables cropping of images.
|
||||
* @default true
|
||||
|
||||
@@ -24,7 +24,6 @@ type CreateAccessPromise = (args: {
|
||||
access: Access | FieldAccess
|
||||
accessLevel: 'entity' | 'field'
|
||||
disableWhere?: boolean
|
||||
locale?: string
|
||||
operation: AllOperations
|
||||
policiesObj: {
|
||||
[key: string]: any
|
||||
@@ -105,7 +104,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
await docBeingAccessed
|
||||
|
||||
// https://payloadcms.slack.com/archives/C048Z9C2BEX/p1702054928343769
|
||||
const accessResult = await access({ id, data, doc: docBeingAccessed, locale, req })
|
||||
const accessResult = await access({ id, data, doc: docBeingAccessed, req })
|
||||
|
||||
if (typeof accessResult === 'object' && !disableWhere) {
|
||||
mutablePolicies[operation] = {
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { PayloadRequest } from '../types/index.js'
|
||||
|
||||
import { createPayloadRequest } from './createPayloadRequest.js'
|
||||
import { headersWithCors } from './headersWithCors.js'
|
||||
import { mergeHeaders } from './mergeHeaders.js'
|
||||
import { routeError } from './routeError.js'
|
||||
|
||||
const notFoundResponse = (req: PayloadRequest) => {
|
||||
@@ -82,6 +83,7 @@ export const handleEndpoints = async ({
|
||||
|
||||
const url = `${request.url}?${new URLSearchParams(search).toString()}`
|
||||
const response = await handleEndpoints({
|
||||
basePath,
|
||||
config: incomingConfig,
|
||||
request: new Request(url, {
|
||||
cache: request.cache,
|
||||
@@ -215,14 +217,11 @@ export const handleEndpoints = async ({
|
||||
}
|
||||
|
||||
const response = await handler(req)
|
||||
|
||||
if (req.responseHeaders) {
|
||||
for (const [key, value] of req.responseHeaders) {
|
||||
response.headers.append(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
return new Response(response.body, {
|
||||
headers: mergeHeaders(req.responseHeaders ?? new Headers(), response.headers),
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
} catch (err) {
|
||||
return routeError({
|
||||
collection,
|
||||
|
||||
@@ -21,8 +21,9 @@ export const deleteCollectionVersions = async ({ id, slug, payload, req }: Args)
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
payload.logger.error(
|
||||
`There was an error removing versions for the deleted ${slug} document with ID ${id}.`,
|
||||
)
|
||||
payload.logger.error({
|
||||
err,
|
||||
msg: `There was an error removing versions for the deleted ${slug} document with ID ${id}.`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
60
packages/payload/src/versions/deleteScheduledPublishJobs.ts
Normal file
60
packages/payload/src/versions/deleteScheduledPublishJobs.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { PayloadRequest } from '../types/index.js'
|
||||
|
||||
import { type Payload } from '../index.js'
|
||||
|
||||
type Args = {
|
||||
id?: number | string
|
||||
payload: Payload
|
||||
req?: PayloadRequest
|
||||
slug: string
|
||||
}
|
||||
|
||||
export const deleteScheduledPublishJobs = async ({
|
||||
id,
|
||||
slug,
|
||||
payload,
|
||||
req,
|
||||
}: Args): Promise<void> => {
|
||||
try {
|
||||
await payload.db.deleteMany({
|
||||
collection: 'payload-jobs',
|
||||
req,
|
||||
where: {
|
||||
and: [
|
||||
// only want to delete jobs have not run yet
|
||||
{
|
||||
completedAt: {
|
||||
exists: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
processing: {
|
||||
equals: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
'input.doc.value': {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
{
|
||||
'input.doc.relationTo': {
|
||||
equals: slug,
|
||||
},
|
||||
},
|
||||
// data.type narrows scheduled publish jobs in case of another job having input.doc.value
|
||||
{
|
||||
taskSlug: {
|
||||
equals: 'schedulePublish',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
payload.logger.error({
|
||||
err,
|
||||
msg: `There was an error deleting scheduled publish jobs from the queue for ${slug} document with ID ${id}.`,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,11 @@ export const saveVersion = async ({
|
||||
if (draft) {
|
||||
versionData._status = 'draft'
|
||||
}
|
||||
|
||||
if (collection?.timestamps && draft) {
|
||||
versionData.updatedAt = now
|
||||
}
|
||||
|
||||
if (versionData._id) {
|
||||
delete versionData._id
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ import type { CollectionSlug, GlobalSlug } from '../../index.js'
|
||||
export type SchedulePublishTaskInput = {
|
||||
doc?: {
|
||||
relationTo: CollectionSlug
|
||||
value: number | string
|
||||
value: string
|
||||
}
|
||||
global?: GlobalSlug
|
||||
locale?: string
|
||||
type: string
|
||||
type?: string
|
||||
user?: number | string
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../translations" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }, { "path": "../ui" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }, { "path": "../ui" }, { "path": "../next" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-sentry",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "Sentry plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.1",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }, { "path": "../ui" }, { "path": "../next" }]
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user