Merge branch 'main' into fix/parent-labels-in-toast
This commit is contained in:
37
.github/CODEOWNERS
vendored
37
.github/CODEOWNERS
vendored
@@ -1,37 +0,0 @@
|
||||
# Order matters. The last matching pattern takes precedence.
|
||||
|
||||
### Package Exports
|
||||
|
||||
**/exports/ @denolfe @jmikrut @DanRibbens
|
||||
|
||||
### Packages
|
||||
|
||||
/packages/plugin-cloud*/src/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/email-*/src/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/live-preview*/src/ @jacobsfletch
|
||||
/packages/plugin-stripe/src/ @jacobsfletch
|
||||
/packages/plugin-multi-tenant/src/ @JarrodMFlesch
|
||||
/packages/richtext-*/src/ @AlessioGr
|
||||
/packages/next/src/ @jmikrut @jacobsfletch @AlessioGr @JarrodMFlesch
|
||||
/packages/ui/src/ @jmikrut @jacobsfletch @AlessioGr @JarrodMFlesch
|
||||
/packages/storage-*/src/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/create-payload-app/src/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/eslint-*/ @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
|
||||
### Templates
|
||||
|
||||
/templates/_data/ @denolfe @jmikrut @DanRibbens
|
||||
/templates/_template/ @denolfe @jmikrut @DanRibbens
|
||||
|
||||
### Build Files
|
||||
|
||||
**/tsconfig*.json @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
**/jest.config.js @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
|
||||
### Root
|
||||
|
||||
/package.json @denolfe @jmikrut @DanRibbens
|
||||
/tools/ @denolfe @jmikrut @DanRibbens
|
||||
/.husky/ @denolfe @jmikrut @DanRibbens
|
||||
/.vscode/ @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
/.github/ @denolfe @jmikrut @DanRibbens
|
||||
@@ -63,41 +63,41 @@ export default buildConfig({
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
|
||||
| **`bin`** | Register custom bin scripts for Payload to execute. |
|
||||
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
|
||||
| **`db`** * | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
||||
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
|
||||
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
|
||||
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
|
||||
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
|
||||
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
||||
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
|
||||
| **`loggingLevels`** | An object to override the level to use in the logger for Payload's errors. |
|
||||
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
|
||||
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
|
||||
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
|
||||
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
|
||||
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
|
||||
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| **`upload`** | Base Payload upload configuration. [More details](../upload/overview#payload-wide-upload-options). |
|
||||
| **`routes`** | Control the routing structure that Payload binds itself to. [More details](../admin/overview#root-level-routes). |
|
||||
| **`email`** | Configure the Email Adapter for Payload to use. [More details](../email/overview). |
|
||||
| **`onInit`** | A function that is called immediately following startup that receives the Payload instance as its only argument. |
|
||||
| **`debug`** | Enable to expose more detailed error information. |
|
||||
| **`telemetry`** | Disable Payload telemetry by passing `false`. [More details](#telemetry). |
|
||||
| **`hooks`** | An array of Root Hooks. [More details](../hooks/overview). |
|
||||
| **`plugins`** | An array of Plugins. [More details](../plugins/overview). |
|
||||
| **`endpoints`** | An array of Custom Endpoints added to the Payload router. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
||||
| **`i18n`** | Internationalization configuration. Pass all i18n languages you'd like the admin UI to support. Defaults to English-only. [More details](./i18n). |
|
||||
| **`secret`** * | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
|
||||
| **`sharp`** | If you would like Payload to offer cropping, focal point selection, and automatic media resizing, install and pass the Sharp module to the config here. |
|
||||
| **`typescript`** | Configure TypeScript settings here. [More details](#typescript). |
|
||||
| Option | Description |
|
||||
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
|
||||
| **`bin`** | Register custom bin scripts for Payload to execute. [More Details](#custom-bin-scripts). |
|
||||
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
|
||||
| **`db`** * | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
||||
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
|
||||
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
|
||||
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
|
||||
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
|
||||
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
||||
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
|
||||
| **`loggingLevels`** | An object to override the level to use in the logger for Payload's errors. |
|
||||
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
|
||||
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
|
||||
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
|
||||
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
|
||||
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
|
||||
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| **`upload`** | Base Payload upload configuration. [More details](../upload/overview#payload-wide-upload-options). |
|
||||
| **`routes`** | Control the routing structure that Payload binds itself to. [More details](../admin/overview#root-level-routes). |
|
||||
| **`email`** | Configure the Email Adapter for Payload to use. [More details](../email/overview). |
|
||||
| **`onInit`** | A function that is called immediately following startup that receives the Payload instance as its only argument. |
|
||||
| **`debug`** | Enable to expose more detailed error information. |
|
||||
| **`telemetry`** | Disable Payload telemetry by passing `false`. [More details](#telemetry). |
|
||||
| **`hooks`** | An array of Root Hooks. [More details](../hooks/overview). |
|
||||
| **`plugins`** | An array of Plugins. [More details](../plugins/overview). |
|
||||
| **`endpoints`** | An array of Custom Endpoints added to the Payload router. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
||||
| **`i18n`** | Internationalization configuration. Pass all i18n languages you'd like the admin UI to support. Defaults to English-only. [More details](./i18n). |
|
||||
| **`secret`** * | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
|
||||
| **`sharp`** | If you would like Payload to offer cropping, focal point selection, and automatic media resizing, install and pass the Sharp module to the config here. |
|
||||
| **`typescript`** | Configure TypeScript settings here. [More details](#typescript). |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
|
||||
@@ -265,3 +265,43 @@ The Payload Config can accept compatibility flags for running the newest version
|
||||
Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized.
|
||||
|
||||
By default, Payload will remove the `localized: true` property from sub-fields if a parent field is localized. Set this compatibility flag to `true` only if you have an existing Payload MongoDB database from pre-3.0, and you have nested localized fields that you would like to maintain without migrating.
|
||||
|
||||
|
||||
## Custom bin scripts
|
||||
|
||||
Using the `bin` configuration property, you can inject your own scripts to `npx payload`.
|
||||
Example for `pnpm payload seed`:
|
||||
|
||||
|
||||
Step 1: create `seed.ts` file in the same folder with `payload.config.ts` with:
|
||||
|
||||
```ts
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import payload from 'payload'
|
||||
|
||||
// Script must define a "script" function export that accepts the sanitized config
|
||||
export const script = async (config: SanitizedConfig) => {
|
||||
await payload.init({ config })
|
||||
await payload.create({ collection: 'pages', data: { title: 'my title' } })
|
||||
payload.logger.info('Succesffully seeded!')
|
||||
process.exit(0)
|
||||
}
|
||||
```
|
||||
|
||||
Step 2: add the `seed` script to `bin`:
|
||||
```ts
|
||||
export default buildConfig({
|
||||
bin: [
|
||||
{
|
||||
scriptPath: path.resolve(dirname, 'seed.ts'),
|
||||
key: 'seed',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
Now you can run the command using:
|
||||
```sh
|
||||
pnpm payload seed
|
||||
```
|
||||
@@ -276,7 +276,7 @@ export default async function MyServerComponent({
|
||||
|
||||
But, the Payload Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) by design. It is full of custom validation functions and more. This means that the Payload Config, in its entirety, cannot be passed directly to Client Components.
|
||||
|
||||
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](../admin/hooks#useconfig) hook:
|
||||
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](../admin/react-hooks#useconfig) hook:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
@@ -375,7 +375,7 @@ export function MyClientComponent() {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See the [Hooks](../admin/hooks) documentation for a full list of available hooks.
|
||||
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
### Getting the Current Locale
|
||||
@@ -422,12 +422,12 @@ function Greeting() {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See the [Hooks](../admin/hooks) documentation for a full list of available hooks.
|
||||
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
### Using Hooks
|
||||
|
||||
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](../admin/hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can use one of the many hooks available depending on your needs.
|
||||
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](../admin/react-hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can use one of the many hooks available depending on your needs.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
@@ -444,7 +444,7 @@ export function MyClientComponent() {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See the [Hooks](../admin/hooks) documentation for a full list of available hooks.
|
||||
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
### Adding Styles
|
||||
|
||||
@@ -658,7 +658,7 @@ In addition to the above props, all Server Components will also receive the foll
|
||||
|
||||
When swapping out the `Field` component, you are responsible for sending and receiving the field's `value` from the form itself.
|
||||
|
||||
To do so, import the [`useField`](../admin/hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
|
||||
To do so, import the [`useField`](../admin/react-hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
@@ -677,7 +677,7 @@ export const CustomTextField: React.FC = () => {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
For a complete list of all available React hooks, see the [Payload React Hooks](../admin/hooks) documentation. For additional help, see [Building Custom Components](../custom-components/overview#building-custom-components).
|
||||
For a complete list of all available React hooks, see the [Payload React Hooks](../admin/react-hooks) documentation. For additional help, see [Building Custom Components](../custom-components/overview#building-custom-components).
|
||||
</Banner>
|
||||
|
||||
##### TypeScript#field-component-types
|
||||
|
||||
@@ -27,7 +27,7 @@ There are four main types of Hooks in Payload:
|
||||
|
||||
<Banner type="warning">
|
||||
**Reminder:**
|
||||
Payload also ships a set of _React_ hooks that you can use in your frontend application. Although they share a common name, these are very different things and should not be confused. [More details](../admin/hooks).
|
||||
Payload also ships a set of _React_ hooks that you can use in your frontend application. Although they share a common name, these are very different things and should not be confused. [More details](../admin/react-hooks).
|
||||
</Banner>
|
||||
|
||||
## Root Hooks
|
||||
|
||||
@@ -414,6 +414,15 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
```
|
||||
1. The `./src/public` directory is now located directly at root level `./public` [see Next.js docs for details](https://nextjs.org/docs/pages/building-your-application/optimizing/static-assets)
|
||||
|
||||
1. Payload now automatically removes `localized: true` property from sub-fields if a parent is localized, as it's redunant and unnecessary. If you have some existing data in this structure and you want to disable that behavior, you need to enable `allowLocalizedWithinLocalized` flag in your payload.config [read more in documentation](https://payloadcms.com/docs/configuration/overview#compatibility-flags), or create a migration script that aligns your data.
|
||||
Mongodb example for a link in a page layout.
|
||||
|
||||
```diff
|
||||
- layout.columns.en.link.en.type.en
|
||||
+ layout.columns.en.link.type
|
||||
```
|
||||
|
||||
|
||||
## Custom Components
|
||||
|
||||
1. All Payload React components have been moved from the `payload` package to `@payloadcms/ui`. If you were previously importing components into your app from the `payload` package, for example to create Custom Components, you will need to change your import paths:
|
||||
|
||||
@@ -278,6 +278,50 @@ async rewrites() {
|
||||
}
|
||||
```
|
||||
|
||||
### React Hooks
|
||||
|
||||
Below are the hooks exported from the plugin that you can import into your own custom components to consume.
|
||||
|
||||
#### useTenantSelection
|
||||
|
||||
You can import this like so:
|
||||
|
||||
```tsx
|
||||
import { useTenantSelection } from '@payloadcms/plugin-multi-tenant/client'
|
||||
|
||||
...
|
||||
|
||||
const tenantContext = useTenantSelection()
|
||||
```
|
||||
|
||||
The hook returns the following context:
|
||||
|
||||
```ts
|
||||
type ContextType = {
|
||||
/**
|
||||
* Array of options to select from
|
||||
*/
|
||||
options: OptionObject[]
|
||||
/**
|
||||
* The currently selected tenant ID
|
||||
*/
|
||||
selectedTenantID: number | string | undefined
|
||||
/**
|
||||
* Prevents a refresh when the tenant is changed
|
||||
*
|
||||
* If not switching tenants while viewing a "global", set to true
|
||||
*/
|
||||
setPreventRefreshOnChange: React.Dispatch<React.SetStateAction<boolean>>
|
||||
/**
|
||||
* Sets the selected tenant ID
|
||||
*
|
||||
* @param args.id - The ID of the tenant to select
|
||||
* @param args.refresh - Whether to refresh the page after changing the tenant
|
||||
*/
|
||||
setTenant: (args: { id: number | string | undefined; refresh?: boolean }) => void
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -145,13 +145,13 @@ Here's an overview of all the included features:
|
||||
|
||||
| Feature Name | Included by default | Description |
|
||||
| --- | --- | --- |
|
||||
| **`BoldTextFeature`** | Yes | Handles the bold text format |
|
||||
| **`ItalicTextFeature`** | Yes | Handles the italic text format |
|
||||
| **`UnderlineTextFeature`** | Yes | Handles the underline text format |
|
||||
| **`StrikethroughTextFeature`** | Yes | Handles the strikethrough text format |
|
||||
| **`SubscriptTextFeature`** | Yes | Handles the subscript text format |
|
||||
| **`SuperscriptTextFeature`** | Yes | Handles the superscript text format |
|
||||
| **`InlineCodeTextFeature`** | Yes | Handles the inline-code text format |
|
||||
| **`BoldFeature`** | Yes | Handles the bold text format |
|
||||
| **`ItalicFeature`** | Yes | Handles the italic text format |
|
||||
| **`UnderlineFeature`** | Yes | Handles the underline text format |
|
||||
| **`StrikethroughFeature`** | Yes | Handles the strikethrough text format |
|
||||
| **`SubscriptFeature`** | Yes | Handles the subscript text format |
|
||||
| **`SuperscriptFeature`** | Yes | Handles the superscript text format |
|
||||
| **`InlineCodeFeature`** | Yes | Handles the inline-code text format |
|
||||
| **`ParagraphFeature`** | Yes | Handles paragraphs. Since they are already a key feature of lexical itself, this Feature mainly handles the Slash and Add-Block menu entries for paragraphs |
|
||||
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
|
||||
| **`AlignFeature`** | Yes | Allows you to align text left, centered and right |
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { handleError } from './utilities/handleError.js'
|
||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const create: Create = async function create(
|
||||
this: MongooseAdapter,
|
||||
@@ -18,31 +18,31 @@ export const create: Create = async function create(
|
||||
|
||||
let doc
|
||||
|
||||
const sanitizedData = sanitizeRelationshipIDs({
|
||||
config: this.payload.config,
|
||||
transform({
|
||||
adapter: this,
|
||||
data,
|
||||
fields: this.payload.collections[collection].config.fields,
|
||||
operation: 'write',
|
||||
})
|
||||
|
||||
if (this.payload.collections[collection].customIDType) {
|
||||
sanitizedData._id = sanitizedData.id
|
||||
data._id = data.id
|
||||
}
|
||||
|
||||
try {
|
||||
;[doc] = await Model.create([sanitizedData], options)
|
||||
;[doc] = await Model.create([data], options)
|
||||
} catch (error) {
|
||||
handleError({ collection, error, req })
|
||||
}
|
||||
|
||||
// doc.toJSON does not do stuff like converting ObjectIds to string, or date strings to date objects. That's why we use JSON.parse/stringify here
|
||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||
const verificationToken = doc._verificationToken
|
||||
doc = doc.toObject()
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
if (verificationToken) {
|
||||
result._verificationToken = verificationToken
|
||||
}
|
||||
transform({
|
||||
adapter: this,
|
||||
data: doc,
|
||||
fields: this.payload.collections[collection].config.fields,
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return result
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ import type { CreateGlobal } from 'payload'
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const createGlobal: CreateGlobal = async function createGlobal(
|
||||
this: MongooseAdapter,
|
||||
@@ -13,26 +12,28 @@ export const createGlobal: CreateGlobal = async function createGlobal(
|
||||
) {
|
||||
const Model = this.globals
|
||||
|
||||
const global = sanitizeRelationshipIDs({
|
||||
config: this.payload.config,
|
||||
data: {
|
||||
globalType: slug,
|
||||
...data,
|
||||
},
|
||||
transform({
|
||||
adapter: this,
|
||||
data,
|
||||
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
|
||||
globalSlug: slug,
|
||||
operation: 'write',
|
||||
})
|
||||
|
||||
const options: CreateOptions = {
|
||||
session: await getSession(this, req),
|
||||
}
|
||||
|
||||
let [result] = (await Model.create([global], options)) as any
|
||||
let [result] = (await Model.create([data], options)) as any
|
||||
|
||||
result = JSON.parse(JSON.stringify(result))
|
||||
result = result.toObject()
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
result = sanitizeInternalFields(result)
|
||||
transform({
|
||||
adapter: this,
|
||||
data: result,
|
||||
fields: this.payload.config.globals.find((globalConfig) => globalConfig.slug === slug).fields,
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { CreateOptions } from 'mongoose'
|
||||
|
||||
import { buildVersionGlobalFields, type CreateGlobalVersion, type Document } from 'payload'
|
||||
import { buildVersionGlobalFields, type CreateGlobalVersion } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(
|
||||
this: MongooseAdapter,
|
||||
@@ -26,25 +26,30 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
||||
session: await getSession(this, req),
|
||||
}
|
||||
|
||||
const data = sanitizeRelationshipIDs({
|
||||
config: this.payload.config,
|
||||
data: {
|
||||
autosave,
|
||||
createdAt,
|
||||
latest: true,
|
||||
parent,
|
||||
publishedLocale,
|
||||
snapshot,
|
||||
updatedAt,
|
||||
version: versionData,
|
||||
},
|
||||
fields: buildVersionGlobalFields(
|
||||
this.payload.config,
|
||||
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
||||
),
|
||||
const data = {
|
||||
autosave,
|
||||
createdAt,
|
||||
latest: true,
|
||||
parent,
|
||||
publishedLocale,
|
||||
snapshot,
|
||||
updatedAt,
|
||||
version: versionData,
|
||||
}
|
||||
|
||||
const fields = buildVersionGlobalFields(
|
||||
this.payload.config,
|
||||
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
||||
)
|
||||
|
||||
transform({
|
||||
adapter: this,
|
||||
data,
|
||||
fields,
|
||||
operation: 'write',
|
||||
})
|
||||
|
||||
const [doc] = await VersionModel.create([data], options, req)
|
||||
let [doc] = await VersionModel.create([data], options, req)
|
||||
|
||||
await VersionModel.updateMany(
|
||||
{
|
||||
@@ -70,13 +75,14 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
||||
options,
|
||||
)
|
||||
|
||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||
const verificationToken = doc._verificationToken
|
||||
doc = doc.toObject()
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
if (verificationToken) {
|
||||
result._verificationToken = verificationToken
|
||||
}
|
||||
return result
|
||||
transform({
|
||||
adapter: this,
|
||||
data: doc,
|
||||
fields,
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { CreateOptions } from 'mongoose'
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
import { buildVersionCollectionFields, type CreateVersion, type Document } from 'payload'
|
||||
import { buildVersionCollectionFields, type CreateVersion } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const createVersion: CreateVersion = async function createVersion(
|
||||
this: MongooseAdapter,
|
||||
@@ -27,25 +26,30 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
session: await getSession(this, req),
|
||||
}
|
||||
|
||||
const data = sanitizeRelationshipIDs({
|
||||
config: this.payload.config,
|
||||
data: {
|
||||
autosave,
|
||||
createdAt,
|
||||
latest: true,
|
||||
parent,
|
||||
publishedLocale,
|
||||
snapshot,
|
||||
updatedAt,
|
||||
version: versionData,
|
||||
},
|
||||
fields: buildVersionCollectionFields(
|
||||
this.payload.config,
|
||||
this.payload.collections[collectionSlug].config,
|
||||
),
|
||||
const data = {
|
||||
autosave,
|
||||
createdAt,
|
||||
latest: true,
|
||||
parent,
|
||||
publishedLocale,
|
||||
snapshot,
|
||||
updatedAt,
|
||||
version: versionData,
|
||||
}
|
||||
|
||||
const fields = buildVersionCollectionFields(
|
||||
this.payload.config,
|
||||
this.payload.collections[collectionSlug].config,
|
||||
)
|
||||
|
||||
transform({
|
||||
adapter: this,
|
||||
data,
|
||||
fields,
|
||||
operation: 'write',
|
||||
})
|
||||
|
||||
const [doc] = await VersionModel.create([data], options, req)
|
||||
let [doc] = await VersionModel.create([data], options, req)
|
||||
|
||||
const parentQuery = {
|
||||
$or: [
|
||||
@@ -56,13 +60,6 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
},
|
||||
],
|
||||
}
|
||||
if (data.parent instanceof Types.ObjectId) {
|
||||
parentQuery.$or.push({
|
||||
parent: {
|
||||
$eq: data.parent.toString(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await VersionModel.updateMany(
|
||||
{
|
||||
@@ -89,13 +86,14 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
options,
|
||||
)
|
||||
|
||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||
const verificationToken = doc._verificationToken
|
||||
doc = doc.toObject()
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
if (verificationToken) {
|
||||
result._verificationToken = verificationToken
|
||||
}
|
||||
return result
|
||||
transform({
|
||||
adapter: this,
|
||||
data: doc,
|
||||
fields,
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { DeleteOne, Document } from 'payload'
|
||||
import type { DeleteOne } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const deleteOne: DeleteOne = async function deleteOne(
|
||||
this: MongooseAdapter,
|
||||
@@ -35,11 +35,12 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
||||
return null
|
||||
}
|
||||
|
||||
let result: Document = JSON.parse(JSON.stringify(doc))
|
||||
transform({
|
||||
adapter: this,
|
||||
data: doc,
|
||||
fields: this.payload.collections[collection].config.fields,
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
result = sanitizeInternalFields(result)
|
||||
|
||||
return result
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { buildSortParam } from './queries/buildSortParam.js'
|
||||
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const find: Find = async function find(
|
||||
this: MongooseAdapter,
|
||||
@@ -133,13 +133,12 @@ export const find: Find = async function find(
|
||||
result = await Model.paginate(query, paginationOptions)
|
||||
}
|
||||
|
||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
||||
transform({
|
||||
adapter: this,
|
||||
data: result.docs,
|
||||
fields: this.payload.collections[collection].config.fields,
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return {
|
||||
...result,
|
||||
docs: docs.map((doc) => {
|
||||
doc.id = doc._id
|
||||
return sanitizeInternalFields(doc)
|
||||
}),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -8,14 +8,15 @@ import type { MongooseAdapter } from './index.js'
|
||||
import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const findGlobal: FindGlobal = async function findGlobal(
|
||||
this: MongooseAdapter,
|
||||
{ slug, locale, req, select, where },
|
||||
) {
|
||||
const Model = this.globals
|
||||
const fields = this.payload.globals.config.find((each) => each.slug === slug).flattenedFields
|
||||
const globalConfig = this.payload.globals.config.find((each) => each.slug === slug)
|
||||
const fields = globalConfig.flattenedFields
|
||||
const options: QueryOptions = {
|
||||
lean: true,
|
||||
select: buildProjectionFromSelect({
|
||||
@@ -34,18 +35,18 @@ export const findGlobal: FindGlobal = async function findGlobal(
|
||||
where: combineQueries({ globalType: { equals: slug } }, where),
|
||||
})
|
||||
|
||||
let doc = (await Model.findOne(query, {}, options)) as any
|
||||
const doc = (await Model.findOne(query, {}, options)) as any
|
||||
|
||||
if (!doc) {
|
||||
return null
|
||||
}
|
||||
if (doc._id) {
|
||||
doc.id = doc._id
|
||||
delete doc._id
|
||||
}
|
||||
|
||||
doc = JSON.parse(JSON.stringify(doc))
|
||||
doc = sanitizeInternalFields(doc)
|
||||
transform({
|
||||
adapter: this,
|
||||
data: doc,
|
||||
fields: globalConfig.fields,
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -9,18 +9,15 @@ import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildSortParam } from './queries/buildSortParam.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||
this: MongooseAdapter,
|
||||
{ global, limit, locale, page, pagination, req, select, skip, sort: sortArg, where },
|
||||
) {
|
||||
const globalConfig = this.payload.globals.config.find(({ slug }) => slug === global)
|
||||
const Model = this.versions[global]
|
||||
const versionFields = buildVersionGlobalFields(
|
||||
this.payload.config,
|
||||
this.payload.globals.config.find(({ slug }) => slug === global),
|
||||
true,
|
||||
)
|
||||
const versionFields = buildVersionGlobalFields(this.payload.config, globalConfig, true)
|
||||
|
||||
const session = await getSession(this, req)
|
||||
const options: QueryOptions = {
|
||||
@@ -103,13 +100,13 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
}
|
||||
|
||||
const result = await Model.paginate(query, paginationOptions)
|
||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
||||
|
||||
return {
|
||||
...result,
|
||||
docs: docs.map((doc) => {
|
||||
doc.id = doc._id
|
||||
return sanitizeInternalFields(doc)
|
||||
}),
|
||||
}
|
||||
transform({
|
||||
adapter: this,
|
||||
data: result.docs,
|
||||
fields: buildVersionGlobalFields(this.payload.config, globalConfig),
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AggregateOptions, QueryOptions } from 'mongoose'
|
||||
import type { Document, FindOne } from 'payload'
|
||||
import type { FindOne } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const findOne: FindOne = async function findOne(
|
||||
this: MongooseAdapter,
|
||||
@@ -58,11 +58,7 @@ export const findOne: FindOne = async function findOne(
|
||||
return null
|
||||
}
|
||||
|
||||
let result: Document = JSON.parse(JSON.stringify(doc))
|
||||
transform({ adapter: this, data: doc, fields: collectionConfig.fields, operation: 'read' })
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
result = sanitizeInternalFields(result)
|
||||
|
||||
return result
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildSortParam } from './queries/buildSortParam.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const findVersions: FindVersions = async function findVersions(
|
||||
this: MongooseAdapter,
|
||||
@@ -104,13 +104,13 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
}
|
||||
|
||||
const result = await Model.paginate(query, paginationOptions)
|
||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
||||
|
||||
return {
|
||||
...result,
|
||||
docs: docs.map((doc) => {
|
||||
doc.id = doc._id
|
||||
return sanitizeInternalFields(doc)
|
||||
}),
|
||||
}
|
||||
transform({
|
||||
adapter: this,
|
||||
data: result.docs,
|
||||
fields: buildVersionCollectionFields(this.payload.config, collectionConfig),
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -476,6 +476,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
||||
schemaToReturn = {
|
||||
_id: false,
|
||||
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
||||
let localeSchema: { [key: string]: any } = {}
|
||||
|
||||
@@ -698,6 +699,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
||||
schemaToReturn = {
|
||||
_id: false,
|
||||
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
||||
let localeSchema: { [key: string]: any } = {}
|
||||
|
||||
|
||||
@@ -6,11 +6,12 @@ import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
import { getSession } from '../utilities/getSession.js'
|
||||
import { sanitizeRelationshipIDs } from '../utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from '../utilities/transform.js'
|
||||
|
||||
const migrateModelWithBatching = async ({
|
||||
batchSize,
|
||||
config,
|
||||
db,
|
||||
fields,
|
||||
Model,
|
||||
parentIsLocalized,
|
||||
@@ -18,6 +19,7 @@ const migrateModelWithBatching = async ({
|
||||
}: {
|
||||
batchSize: number
|
||||
config: SanitizedConfig
|
||||
db: MongooseAdapter
|
||||
fields: Field[]
|
||||
Model: Model<any>
|
||||
parentIsLocalized: boolean
|
||||
@@ -49,7 +51,7 @@ const migrateModelWithBatching = async ({
|
||||
}
|
||||
|
||||
for (const doc of docs) {
|
||||
sanitizeRelationshipIDs({ config, data: doc, fields, parentIsLocalized })
|
||||
transform({ adapter: db, data: doc, fields, operation: 'write', parentIsLocalized })
|
||||
}
|
||||
|
||||
await Model.collection.bulkWrite(
|
||||
@@ -124,6 +126,7 @@ export async function migrateRelationshipsV2_V3({
|
||||
await migrateModelWithBatching({
|
||||
batchSize,
|
||||
config,
|
||||
db,
|
||||
fields: collection.fields,
|
||||
Model: db.collections[collection.slug],
|
||||
parentIsLocalized: false,
|
||||
@@ -139,6 +142,7 @@ export async function migrateRelationshipsV2_V3({
|
||||
await migrateModelWithBatching({
|
||||
batchSize,
|
||||
config,
|
||||
db,
|
||||
fields: buildVersionCollectionFields(config, collection),
|
||||
Model: db.versions[collection.slug],
|
||||
parentIsLocalized: false,
|
||||
@@ -167,10 +171,11 @@ export async function migrateRelationshipsV2_V3({
|
||||
|
||||
// in case if the global doesn't exist in the database yet (not saved)
|
||||
if (doc) {
|
||||
sanitizeRelationshipIDs({
|
||||
config,
|
||||
transform({
|
||||
adapter: db,
|
||||
data: doc,
|
||||
fields: global.fields,
|
||||
operation: 'write',
|
||||
})
|
||||
|
||||
await GlobalsModel.collection.updateOne(
|
||||
@@ -191,6 +196,7 @@ export async function migrateRelationshipsV2_V3({
|
||||
await migrateModelWithBatching({
|
||||
batchSize,
|
||||
config,
|
||||
db,
|
||||
fields: buildVersionGlobalFields(config, global),
|
||||
Model: db.versions[global.slug],
|
||||
parentIsLocalized: false,
|
||||
|
||||
@@ -255,6 +255,25 @@ export async function buildSearchParam({
|
||||
return result
|
||||
}
|
||||
|
||||
if (formattedOperator === 'not_like' && typeof formattedValue === 'string') {
|
||||
const words = formattedValue.split(' ')
|
||||
|
||||
const result = {
|
||||
value: {
|
||||
$and: words.map((word) => ({
|
||||
[path]: {
|
||||
$not: {
|
||||
$options: 'i',
|
||||
$regex: word.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Some operators like 'near' need to define a full query
|
||||
// so if there is no operator key, just return the value
|
||||
if (!operatorKey) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { buildSortParam } from './queries/buildSortParam.js'
|
||||
import { buildJoinAggregation } from './utilities/buildJoinAggregation.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
this: MongooseAdapter,
|
||||
@@ -124,18 +124,18 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
result = await VersionModel.paginate(versionQuery, paginationOptions)
|
||||
}
|
||||
|
||||
const docs = JSON.parse(JSON.stringify(result.docs))
|
||||
transform({
|
||||
adapter: this,
|
||||
data: result.docs,
|
||||
fields: buildVersionCollectionFields(this.payload.config, collectionConfig),
|
||||
operation: 'read',
|
||||
})
|
||||
|
||||
return {
|
||||
...result,
|
||||
docs: docs.map((doc) => {
|
||||
doc = {
|
||||
_id: doc.parent,
|
||||
id: doc.parent,
|
||||
...doc.version,
|
||||
}
|
||||
|
||||
return sanitizeInternalFields(doc)
|
||||
}),
|
||||
for (let i = 0; i < result.docs.length; i++) {
|
||||
const id = result.docs[i].parent
|
||||
result.docs[i] = result.docs[i].version
|
||||
result.docs[i].id = id
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||
this: MongooseAdapter,
|
||||
@@ -27,25 +26,11 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||
session: await getSession(this, req),
|
||||
}
|
||||
|
||||
let result
|
||||
transform({ adapter: this, data, fields, globalSlug: slug, operation: 'write' })
|
||||
|
||||
const sanitizedData = sanitizeRelationshipIDs({
|
||||
config: this.payload.config,
|
||||
data,
|
||||
fields,
|
||||
})
|
||||
const result: any = await Model.findOneAndUpdate({ globalType: slug }, data, options)
|
||||
|
||||
result = await Model.findOneAndUpdate({ globalType: slug }, sanitizedData, options)
|
||||
|
||||
if (!result) {
|
||||
return null
|
||||
}
|
||||
|
||||
result = JSON.parse(JSON.stringify(result))
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
result = sanitizeInternalFields(result)
|
||||
transform({ adapter: this, data: result, fields, globalSlug: slug, operation: 'read' })
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { MongooseAdapter } from './index.js'
|
||||
import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
this: MongooseAdapter,
|
||||
@@ -47,26 +47,15 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
where: whereToUse,
|
||||
})
|
||||
|
||||
const sanitizedData = sanitizeRelationshipIDs({
|
||||
config: this.payload.config,
|
||||
data: versionData,
|
||||
fields,
|
||||
})
|
||||
transform({ adapter: this, data: versionData, fields, operation: 'write' })
|
||||
|
||||
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
|
||||
const doc = await VersionModel.findOneAndUpdate(query, versionData, options)
|
||||
|
||||
if (!doc) {
|
||||
return null
|
||||
}
|
||||
|
||||
const result = JSON.parse(JSON.stringify(doc))
|
||||
transform({ adapter: this, data: doc, fields, operation: 'read' })
|
||||
|
||||
const verificationToken = doc._verificationToken
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
if (verificationToken) {
|
||||
result._verificationToken = verificationToken
|
||||
}
|
||||
return result
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { handleError } from './utilities/handleError.js'
|
||||
import { sanitizeInternalFields } from './utilities/sanitizeInternalFields.js'
|
||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const updateOne: UpdateOne = async function updateOne(
|
||||
this: MongooseAdapter,
|
||||
@@ -39,14 +38,10 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
|
||||
let result
|
||||
|
||||
const sanitizedData = sanitizeRelationshipIDs({
|
||||
config: this.payload.config,
|
||||
data,
|
||||
fields,
|
||||
})
|
||||
transform({ adapter: this, data, fields, operation: 'write' })
|
||||
|
||||
try {
|
||||
result = await Model.findOneAndUpdate(query, sanitizedData, options)
|
||||
result = await Model.findOneAndUpdate(query, data, options)
|
||||
} catch (error) {
|
||||
handleError({ collection, error, req })
|
||||
}
|
||||
@@ -55,9 +50,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
return null
|
||||
}
|
||||
|
||||
result = JSON.parse(JSON.stringify(result))
|
||||
result.id = result._id
|
||||
result = sanitizeInternalFields(result)
|
||||
transform({ adapter: this, data: result, fields, operation: 'read' })
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { MongooseAdapter } from './index.js'
|
||||
import { buildQuery } from './queries/buildQuery.js'
|
||||
import { buildProjectionFromSelect } from './utilities/buildProjectionFromSelect.js'
|
||||
import { getSession } from './utilities/getSession.js'
|
||||
import { sanitizeRelationshipIDs } from './utilities/sanitizeRelationshipIDs.js'
|
||||
import { transform } from './utilities/transform.js'
|
||||
|
||||
export const updateVersion: UpdateVersion = async function updateVersion(
|
||||
this: MongooseAdapter,
|
||||
@@ -45,26 +45,15 @@ export const updateVersion: UpdateVersion = async function updateVersion(
|
||||
where: whereToUse,
|
||||
})
|
||||
|
||||
const sanitizedData = sanitizeRelationshipIDs({
|
||||
config: this.payload.config,
|
||||
data: versionData,
|
||||
fields,
|
||||
})
|
||||
transform({ adapter: this, data: versionData, fields, operation: 'write' })
|
||||
|
||||
const doc = await VersionModel.findOneAndUpdate(query, sanitizedData, options)
|
||||
const doc = await VersionModel.findOneAndUpdate(query, versionData, options)
|
||||
|
||||
if (!doc) {
|
||||
return null
|
||||
}
|
||||
|
||||
const result = JSON.parse(JSON.stringify(doc))
|
||||
transform({ adapter: this, data: doc, fields, operation: 'write' })
|
||||
|
||||
const verificationToken = doc._verificationToken
|
||||
|
||||
// custom id type reset
|
||||
result.id = result._id
|
||||
if (verificationToken) {
|
||||
result._verificationToken = verificationToken
|
||||
}
|
||||
return result
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -195,17 +195,17 @@ export const buildJoinAggregation = async ({
|
||||
const sliceValue = page ? [(page - 1) * limitJoin, limitJoin] : [limitJoin]
|
||||
|
||||
aggregate.push({
|
||||
$set: {
|
||||
[`${as}.docs`]: {
|
||||
$slice: [`$${as}.docs`, ...sliceValue],
|
||||
$addFields: {
|
||||
[`${as}.hasNextPage`]: {
|
||||
$gt: [{ $size: `$${as}.docs` }, limitJoin || Number.MAX_VALUE],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
aggregate.push({
|
||||
$addFields: {
|
||||
[`${as}.hasNextPage`]: {
|
||||
$gt: [{ $size: `$${as}.docs` }, limitJoin || Number.MAX_VALUE],
|
||||
$set: {
|
||||
[`${as}.docs`]: {
|
||||
$slice: [`$${as}.docs`, ...sliceValue],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
const internalFields = ['__v']
|
||||
|
||||
export const sanitizeInternalFields = <T extends Record<string, unknown>>(incomingDoc: T): T =>
|
||||
Object.entries(incomingDoc).reduce((newDoc, [key, val]): T => {
|
||||
if (key === '_id') {
|
||||
return {
|
||||
...newDoc,
|
||||
id: val,
|
||||
}
|
||||
}
|
||||
|
||||
if (internalFields.indexOf(key) > -1) {
|
||||
return newDoc
|
||||
}
|
||||
|
||||
return {
|
||||
...newDoc,
|
||||
[key]: val,
|
||||
}
|
||||
}, {} as T)
|
||||
@@ -1,165 +0,0 @@
|
||||
import type { CollectionConfig, Field, SanitizedConfig, TraverseFieldsCallback } from 'payload'
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
import { traverseFields } from 'payload'
|
||||
import { fieldAffectsData, fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
type Args = {
|
||||
config: SanitizedConfig
|
||||
data: Record<string, unknown>
|
||||
fields: Field[]
|
||||
parentIsLocalized?: boolean
|
||||
}
|
||||
|
||||
interface RelationObject {
|
||||
relationTo: string
|
||||
value: number | string
|
||||
}
|
||||
|
||||
function isValidRelationObject(value: unknown): value is RelationObject {
|
||||
return typeof value === 'object' && value !== null && 'relationTo' in value && 'value' in value
|
||||
}
|
||||
|
||||
const convertValue = ({
|
||||
relatedCollection,
|
||||
value,
|
||||
}: {
|
||||
relatedCollection: CollectionConfig
|
||||
value: number | string
|
||||
}): number | string | Types.ObjectId => {
|
||||
const customIDField = relatedCollection.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
|
||||
if (customIDField) {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
return new Types.ObjectId(value)
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const sanitizeRelationship = ({ config, field, locale, ref, value }) => {
|
||||
let relatedCollection: CollectionConfig | undefined
|
||||
let result = value
|
||||
|
||||
const hasManyRelations = typeof field.relationTo !== 'string'
|
||||
|
||||
if (!hasManyRelations) {
|
||||
relatedCollection = config.collections?.find(({ slug }) => slug === field.relationTo)
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
result = value.map((val) => {
|
||||
// Handle has many
|
||||
if (relatedCollection && val && (typeof val === 'string' || typeof val === 'number')) {
|
||||
return convertValue({
|
||||
relatedCollection,
|
||||
value: val,
|
||||
})
|
||||
}
|
||||
|
||||
// Handle has many - polymorphic
|
||||
if (isValidRelationObject(val)) {
|
||||
const relatedCollectionForSingleValue = config.collections?.find(
|
||||
({ slug }) => slug === val.relationTo,
|
||||
)
|
||||
|
||||
if (relatedCollectionForSingleValue) {
|
||||
return {
|
||||
relationTo: val.relationTo,
|
||||
value: convertValue({
|
||||
relatedCollection: relatedCollectionForSingleValue,
|
||||
value: val.value,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return val
|
||||
})
|
||||
}
|
||||
|
||||
// Handle has one - polymorphic
|
||||
if (isValidRelationObject(value)) {
|
||||
relatedCollection = config.collections?.find(({ slug }) => slug === value.relationTo)
|
||||
|
||||
if (relatedCollection) {
|
||||
result = {
|
||||
relationTo: value.relationTo,
|
||||
value: convertValue({ relatedCollection, value: value.value }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle has one
|
||||
if (relatedCollection && value && (typeof value === 'string' || typeof value === 'number')) {
|
||||
result = convertValue({
|
||||
relatedCollection,
|
||||
value,
|
||||
})
|
||||
}
|
||||
if (locale) {
|
||||
ref[locale] = result
|
||||
} else {
|
||||
ref[field.name] = result
|
||||
}
|
||||
}
|
||||
|
||||
export const sanitizeRelationshipIDs = ({
|
||||
config,
|
||||
data,
|
||||
fields,
|
||||
parentIsLocalized,
|
||||
}: Args): Record<string, unknown> => {
|
||||
const sanitize: TraverseFieldsCallback = ({ field, ref }) => {
|
||||
if (!ref || typeof ref !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
if (field.type === 'relationship' || field.type === 'upload') {
|
||||
if (!ref[field.name]) {
|
||||
return
|
||||
}
|
||||
|
||||
// handle localized relationships
|
||||
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
const locales = config.localization.locales
|
||||
const fieldRef = ref[field.name]
|
||||
if (typeof fieldRef !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
for (const { code } of locales) {
|
||||
const value = ref[field.name][code]
|
||||
if (value) {
|
||||
sanitizeRelationship({ config, field, locale: code, ref: fieldRef, value })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// handle non-localized relationships
|
||||
sanitizeRelationship({
|
||||
config,
|
||||
field,
|
||||
locale: undefined,
|
||||
ref,
|
||||
value: ref[field.name],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
callback: sanitize,
|
||||
config,
|
||||
fields,
|
||||
fillEmpty: false,
|
||||
parentIsLocalized,
|
||||
ref: data,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import { flattenAllFields, type Field, type SanitizedConfig } from 'payload'
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
|
||||
import { sanitizeRelationshipIDs } from './sanitizeRelationshipIDs.js'
|
||||
import { transform } from './transform.js'
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
const flattenRelationshipValues = (obj: Record<string, any>, prefix = ''): Record<string, any> => {
|
||||
return Object.keys(obj).reduce(
|
||||
@@ -297,7 +298,7 @@ const relsData = {
|
||||
},
|
||||
}
|
||||
|
||||
describe('sanitizeRelationshipIDs', () => {
|
||||
describe('transform', () => {
|
||||
it('should sanitize relationships', () => {
|
||||
const data = {
|
||||
...relsData,
|
||||
@@ -382,7 +383,18 @@ describe('sanitizeRelationshipIDs', () => {
|
||||
}
|
||||
const flattenValuesBefore = Object.values(flattenRelationshipValues(data))
|
||||
|
||||
sanitizeRelationshipIDs({ config, data, fields: config.collections[0].fields })
|
||||
const mockAdapter = {
|
||||
payload: {
|
||||
config,
|
||||
},
|
||||
} as MongooseAdapter
|
||||
|
||||
transform({
|
||||
adapter: mockAdapter,
|
||||
operation: 'write',
|
||||
data,
|
||||
fields: config.collections[0].fields,
|
||||
})
|
||||
const flattenValuesAfter = Object.values(flattenRelationshipValues(data))
|
||||
|
||||
flattenValuesAfter.forEach((value, i) => {
|
||||
347
packages/db-mongodb/src/utilities/transform.ts
Normal file
347
packages/db-mongodb/src/utilities/transform.ts
Normal file
@@ -0,0 +1,347 @@
|
||||
import type {
|
||||
CollectionConfig,
|
||||
DateField,
|
||||
Field,
|
||||
JoinField,
|
||||
RelationshipField,
|
||||
SanitizedConfig,
|
||||
TraverseFieldsCallback,
|
||||
UploadField,
|
||||
} from 'payload'
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
import { traverseFields } from 'payload'
|
||||
import { fieldAffectsData, fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
interface RelationObject {
|
||||
relationTo: string
|
||||
value: number | string
|
||||
}
|
||||
|
||||
function isValidRelationObject(value: unknown): value is RelationObject {
|
||||
return typeof value === 'object' && value !== null && 'relationTo' in value && 'value' in value
|
||||
}
|
||||
|
||||
const convertRelationshipValue = ({
|
||||
operation,
|
||||
relatedCollection,
|
||||
validateRelationships,
|
||||
value,
|
||||
}: {
|
||||
operation: Args['operation']
|
||||
relatedCollection: CollectionConfig
|
||||
validateRelationships?: boolean
|
||||
value: unknown
|
||||
}) => {
|
||||
const customIDField = relatedCollection.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
|
||||
if (operation === 'read') {
|
||||
if (value instanceof Types.ObjectId) {
|
||||
return value.toHexString()
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
if (customIDField) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return new Types.ObjectId(value)
|
||||
} catch (e) {
|
||||
if (validateRelationships) {
|
||||
throw e
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
const sanitizeRelationship = ({
|
||||
config,
|
||||
field,
|
||||
locale,
|
||||
operation,
|
||||
ref,
|
||||
validateRelationships,
|
||||
value,
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
field: JoinField | RelationshipField | UploadField
|
||||
locale?: string
|
||||
operation: Args['operation']
|
||||
ref: Record<string, unknown>
|
||||
validateRelationships?: boolean
|
||||
value?: unknown
|
||||
}) => {
|
||||
if (field.type === 'join') {
|
||||
if (
|
||||
operation === 'read' &&
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
'docs' in value &&
|
||||
Array.isArray(value.docs)
|
||||
) {
|
||||
for (let i = 0; i < value.docs.length; i++) {
|
||||
const item = value.docs[i]
|
||||
|
||||
if (item instanceof Types.ObjectId) {
|
||||
value.docs[i] = item.toHexString()
|
||||
} else if (Array.isArray(field.collection) && item) {
|
||||
// Fields here for polymorphic joins cannot be determinted, JSON.parse needed
|
||||
value.docs[i] = JSON.parse(JSON.stringify(value.docs[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
let relatedCollection: CollectionConfig | undefined
|
||||
let result = value
|
||||
|
||||
const hasManyRelations = typeof field.relationTo !== 'string'
|
||||
|
||||
if (!hasManyRelations) {
|
||||
relatedCollection = config.collections?.find(({ slug }) => slug === field.relationTo)
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
result = value.map((val) => {
|
||||
// Handle has many - polymorphic
|
||||
if (isValidRelationObject(val)) {
|
||||
const relatedCollectionForSingleValue = config.collections?.find(
|
||||
({ slug }) => slug === val.relationTo,
|
||||
)
|
||||
|
||||
if (relatedCollectionForSingleValue) {
|
||||
return {
|
||||
relationTo: val.relationTo,
|
||||
value: convertRelationshipValue({
|
||||
operation,
|
||||
relatedCollection: relatedCollectionForSingleValue,
|
||||
validateRelationships,
|
||||
value: val.value,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (relatedCollection) {
|
||||
return convertRelationshipValue({
|
||||
operation,
|
||||
relatedCollection,
|
||||
validateRelationships,
|
||||
value: val,
|
||||
})
|
||||
}
|
||||
|
||||
return val
|
||||
})
|
||||
}
|
||||
// Handle has one - polymorphic
|
||||
else if (isValidRelationObject(value)) {
|
||||
relatedCollection = config.collections?.find(({ slug }) => slug === value.relationTo)
|
||||
|
||||
if (relatedCollection) {
|
||||
result = {
|
||||
relationTo: value.relationTo,
|
||||
value: convertRelationshipValue({
|
||||
operation,
|
||||
relatedCollection,
|
||||
validateRelationships,
|
||||
value: value.value,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle has one
|
||||
else if (relatedCollection) {
|
||||
result = convertRelationshipValue({
|
||||
operation,
|
||||
relatedCollection,
|
||||
validateRelationships,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
if (locale) {
|
||||
ref[locale] = result
|
||||
} else {
|
||||
ref[field.name] = result
|
||||
}
|
||||
}
|
||||
|
||||
const sanitizeDate = ({
|
||||
field,
|
||||
locale,
|
||||
ref,
|
||||
value,
|
||||
}: {
|
||||
field: DateField
|
||||
locale?: string
|
||||
ref: Record<string, unknown>
|
||||
value: unknown
|
||||
}) => {
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
value = value.toISOString()
|
||||
}
|
||||
|
||||
if (locale) {
|
||||
ref[locale] = value
|
||||
} else {
|
||||
ref[field.name] = value
|
||||
}
|
||||
}
|
||||
|
||||
type Args = {
|
||||
/** instance of the adapter */
|
||||
adapter: MongooseAdapter
|
||||
/** data to transform, can be an array of documents or a single document */
|
||||
data: Record<string, unknown> | Record<string, unknown>[]
|
||||
/** fields accossiated with the data */
|
||||
fields: Field[]
|
||||
/** slug of the global, pass only when the operation is `write` */
|
||||
globalSlug?: string
|
||||
/**
|
||||
* Type of the operation
|
||||
* read - sanitizes ObjectIDs, Date to strings.
|
||||
* write - sanitizes string relationships to ObjectIDs.
|
||||
*/
|
||||
operation: 'read' | 'write'
|
||||
parentIsLocalized?: boolean
|
||||
/**
|
||||
* Throw errors on invalid relationships
|
||||
* @default true
|
||||
*/
|
||||
validateRelationships?: boolean
|
||||
}
|
||||
|
||||
export const transform = ({
|
||||
adapter,
|
||||
data,
|
||||
fields,
|
||||
globalSlug,
|
||||
operation,
|
||||
parentIsLocalized,
|
||||
validateRelationships = true,
|
||||
}: Args) => {
|
||||
if (Array.isArray(data)) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
transform({ adapter, data: data[i], fields, globalSlug, operation, validateRelationships })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
payload: { config },
|
||||
} = adapter
|
||||
|
||||
if (operation === 'read') {
|
||||
delete data['__v']
|
||||
data.id = data._id
|
||||
delete data['_id']
|
||||
|
||||
if (data.id instanceof Types.ObjectId) {
|
||||
data.id = data.id.toHexString()
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'write' && globalSlug) {
|
||||
data.globalType = globalSlug
|
||||
}
|
||||
|
||||
const sanitize: TraverseFieldsCallback = ({ field, ref }) => {
|
||||
if (!ref || typeof ref !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
if (field.type === 'date' && operation === 'read' && ref[field.name]) {
|
||||
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
const fieldRef = ref[field.name]
|
||||
if (!fieldRef || typeof fieldRef !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
for (const locale of config.localization.localeCodes) {
|
||||
sanitizeDate({
|
||||
field,
|
||||
ref: fieldRef,
|
||||
value: fieldRef[locale],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
sanitizeDate({
|
||||
field,
|
||||
ref: ref as Record<string, unknown>,
|
||||
value: ref[field.name],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
field.type === 'relationship' ||
|
||||
field.type === 'upload' ||
|
||||
(operation === 'read' && field.type === 'join')
|
||||
) {
|
||||
if (!ref[field.name]) {
|
||||
return
|
||||
}
|
||||
|
||||
// handle localized relationships
|
||||
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
const locales = config.localization.locales
|
||||
const fieldRef = ref[field.name]
|
||||
if (typeof fieldRef !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
for (const { code } of locales) {
|
||||
const value = ref[field.name][code]
|
||||
if (value) {
|
||||
sanitizeRelationship({
|
||||
config,
|
||||
field,
|
||||
locale: code,
|
||||
operation,
|
||||
ref: fieldRef,
|
||||
validateRelationships,
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// handle non-localized relationships
|
||||
sanitizeRelationship({
|
||||
config,
|
||||
field,
|
||||
locale: undefined,
|
||||
operation,
|
||||
ref: ref as Record<string, unknown>,
|
||||
validateRelationships,
|
||||
value: ref[field.name],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
callback: sanitize,
|
||||
config,
|
||||
fields,
|
||||
fillEmpty: false,
|
||||
parentIsLocalized,
|
||||
ref: data,
|
||||
})
|
||||
}
|
||||
@@ -50,10 +50,12 @@ const createConstraint = ({
|
||||
const newAlias = `${pathSegments[0]}_alias_${pathSegments.length - 1}`
|
||||
let formattedValue = value
|
||||
let formattedOperator = operator
|
||||
|
||||
if (['contains', 'like'].includes(operator)) {
|
||||
formattedOperator = 'like'
|
||||
formattedValue = `%${value}%`
|
||||
} else if (['not_like', 'notlike'].includes(operator)) {
|
||||
formattedOperator = 'not like'
|
||||
formattedValue = `%${value}%`
|
||||
} else if (operator === 'equals') {
|
||||
formattedOperator = '='
|
||||
}
|
||||
@@ -61,7 +63,7 @@ const createConstraint = ({
|
||||
return `EXISTS (
|
||||
SELECT 1
|
||||
FROM json_each(${alias}.value -> '${pathSegments[0]}') AS ${newAlias}
|
||||
WHERE ${newAlias}.value ->> '${pathSegments[1]}' ${formattedOperator} '${formattedValue}'
|
||||
WHERE COALESCE(${newAlias}.value ->> '${pathSegments[1]}', '') ${formattedOperator} '${formattedValue}'
|
||||
)`
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
import { like } from 'drizzle-orm'
|
||||
import { like, notLike } from 'drizzle-orm'
|
||||
import { createDatabaseAdapter, defaultBeginTransaction } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -81,6 +81,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
...operatorMap,
|
||||
contains: like,
|
||||
like,
|
||||
not_like: notLike,
|
||||
} as unknown as Operators
|
||||
|
||||
return createDatabaseAdapter<SQLiteAdapter>({
|
||||
|
||||
@@ -31,9 +31,9 @@ export { buildRawSchema } from './schema/buildRawSchema.js'
|
||||
export { beginTransaction } from './transactions/beginTransaction.js'
|
||||
export { commitTransaction } from './transactions/commitTransaction.js'
|
||||
export { rollbackTransaction } from './transactions/rollbackTransaction.js'
|
||||
export { updateOne } from './update.js'
|
||||
export { updateGlobal } from './updateGlobal.js'
|
||||
export { updateGlobalVersion } from './updateGlobalVersion.js'
|
||||
export { updateOne } from './updateOne.js'
|
||||
export { updateVersion } from './updateVersion.js'
|
||||
export { upsertRow } from './upsertRow/index.js'
|
||||
export { buildCreateMigration } from './utilities/buildCreateMigration.js'
|
||||
|
||||
@@ -7,12 +7,13 @@ const operatorMap: Record<string, string> = {
|
||||
like: 'like_regex',
|
||||
not_equals: '!=',
|
||||
not_in: 'in',
|
||||
not_like: '!like_regex',
|
||||
}
|
||||
|
||||
const sanitizeValue = (value: unknown, operator?: string) => {
|
||||
if (typeof value === 'string') {
|
||||
// ignore casing with like
|
||||
return `"${operator === 'like' ? '(?i)' : ''}${value}"`
|
||||
// ignore casing with like or not_like
|
||||
return `"${['like', 'not_like'].includes(operator) ? '(?i)' : ''}${value}"`
|
||||
}
|
||||
|
||||
return value as string
|
||||
@@ -35,6 +36,10 @@ export const createJSONQuery = ({ column, operator, pathSegments, value }: Creat
|
||||
})
|
||||
} else if (operator === 'exists') {
|
||||
sql = `${value === false ? 'NOT ' : ''}jsonb_path_exists(${columnName}, '$.${jsonPaths}')`
|
||||
} else if (['not_like'].includes(operator)) {
|
||||
const mappedOperator = operatorMap[operator]
|
||||
|
||||
sql = `NOT jsonb_path_exists(${columnName}, '$.${jsonPaths} ? (@ ${mappedOperator.substring(1)} ${sanitizeValue(value, operator)})')`
|
||||
} else {
|
||||
sql = `jsonb_path_exists(${columnName}, '$.${jsonPaths} ? (@ ${operatorMap[operator]} ${sanitizeValue(value, operator)})')`
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
lt,
|
||||
lte,
|
||||
ne,
|
||||
notIlike,
|
||||
notInArray,
|
||||
or,
|
||||
type SQL,
|
||||
@@ -31,6 +32,7 @@ type OperatorKeys =
|
||||
| 'like'
|
||||
| 'not_equals'
|
||||
| 'not_in'
|
||||
| 'not_like'
|
||||
| 'or'
|
||||
|
||||
export type Operators = Record<OperatorKeys, (column: Column, value: SQLWrapper | unknown) => SQL>
|
||||
@@ -48,6 +50,7 @@ export const operatorMap: Operators = {
|
||||
less_than_equal: lte,
|
||||
like: ilike,
|
||||
not_equals: ne,
|
||||
not_like: notIlike,
|
||||
// TODO: support this
|
||||
// all: all,
|
||||
not_in: notInArray,
|
||||
|
||||
@@ -161,6 +161,7 @@ export function parseParams({
|
||||
like: { operator: 'like', wildcard: '%' },
|
||||
not_equals: { operator: '<>', wildcard: '' },
|
||||
not_in: { operator: 'not in', wildcard: '' },
|
||||
not_like: { operator: 'not like', wildcard: '%' },
|
||||
}
|
||||
|
||||
let formattedValue = val
|
||||
@@ -175,11 +176,15 @@ export function parseParams({
|
||||
formattedValue = ''
|
||||
}
|
||||
|
||||
constraints.push(
|
||||
sql.raw(
|
||||
`${table[columnName].name}${jsonQuery} ${operatorKeys[operator].operator} ${formattedValue}`,
|
||||
),
|
||||
)
|
||||
let jsonQuerySelector = `${table[columnName].name}${jsonQuery}`
|
||||
|
||||
if (adapter.name === 'sqlite' && operator === 'not_like') {
|
||||
jsonQuerySelector = `COALESCE(${table[columnName].name}${jsonQuery}, '')`
|
||||
}
|
||||
|
||||
const rawSQLQuery = `${jsonQuerySelector} ${operatorKeys[operator].operator} ${formattedValue}`
|
||||
|
||||
constraints.push(sql.raw(rawSQLQuery))
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { UpdateOne } from 'payload'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from './types.js'
|
||||
|
||||
import { buildFindManyArgs } from './find/buildFindManyArgs.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { selectDistinct } from './queries/selectDistinct.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
@@ -28,6 +28,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
where: whereToUse,
|
||||
})
|
||||
|
||||
// selectDistinct will only return if there are joins
|
||||
const selectDistinctResult = await selectDistinct({
|
||||
adapter: this,
|
||||
chainedMethods: [{ args: [1], method: 'limit' }],
|
||||
@@ -40,22 +41,18 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
|
||||
if (selectDistinctResult?.[0]?.id) {
|
||||
idToUpdate = selectDistinctResult?.[0]?.id
|
||||
|
||||
// If id wasn't passed but `where` without any joins, retrieve it with findFirst
|
||||
} else if (whereArg && !joins.length) {
|
||||
const findManyArgs = buildFindManyArgs({
|
||||
adapter: this,
|
||||
depth: 0,
|
||||
fields: collection.flattenedFields,
|
||||
joinQuery: false,
|
||||
select: {},
|
||||
tableName,
|
||||
})
|
||||
const table = this.tables[tableName]
|
||||
|
||||
findManyArgs.where = where
|
||||
|
||||
const docToUpdate = await db.query[tableName].findFirst(findManyArgs)
|
||||
idToUpdate = docToUpdate?.id
|
||||
const docsToUpdate = await (db as LibSQLDatabase)
|
||||
.select({
|
||||
id: table.id,
|
||||
})
|
||||
.from(table)
|
||||
.where(where)
|
||||
.limit(1)
|
||||
idToUpdate = docsToUpdate?.[0]?.id
|
||||
}
|
||||
|
||||
const result = await upsertRow({
|
||||
@@ -153,7 +153,7 @@ export const renderListView = async (
|
||||
const renderedFilters = renderFilters(collectionConfig.fields, req.payload.importMap)
|
||||
|
||||
const resolvedFilterOptions = await resolveAllFilterOptions({
|
||||
collectionConfig,
|
||||
fields: collectionConfig.fields,
|
||||
req,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import type { CollectionConfig, PayloadRequest, ResolvedFilterOptions } from 'payload'
|
||||
import type { Field, PayloadRequest, ResolvedFilterOptions } from 'payload'
|
||||
|
||||
import { resolveFilterOptions } from '@payloadcms/ui/rsc'
|
||||
import { fieldIsHiddenOrDisabled } from 'payload/shared'
|
||||
import { fieldHasSubFields, fieldIsHiddenOrDisabled } from 'payload/shared'
|
||||
|
||||
export const resolveAllFilterOptions = async ({
|
||||
collectionConfig,
|
||||
fields,
|
||||
req,
|
||||
result,
|
||||
}: {
|
||||
collectionConfig: CollectionConfig
|
||||
fields: Field[]
|
||||
req: PayloadRequest
|
||||
result?: Map<string, ResolvedFilterOptions>
|
||||
}): Promise<Map<string, ResolvedFilterOptions>> => {
|
||||
const resolvedFilterOptions = new Map<string, ResolvedFilterOptions>()
|
||||
const resolvedFilterOptions = !result ? new Map<string, ResolvedFilterOptions>() : result
|
||||
|
||||
await Promise.all(
|
||||
collectionConfig.fields.map(async (field) => {
|
||||
fields.map(async (field) => {
|
||||
if (fieldIsHiddenOrDisabled(field)) {
|
||||
return
|
||||
}
|
||||
@@ -28,8 +30,29 @@ export const resolveAllFilterOptions = async ({
|
||||
siblingData: {}, // use empty object to prevent breaking queries when accessing properties of data
|
||||
user: req.user,
|
||||
})
|
||||
|
||||
resolvedFilterOptions.set(field.name, options)
|
||||
}
|
||||
|
||||
if (fieldHasSubFields(field)) {
|
||||
await resolveAllFilterOptions({
|
||||
fields: field.fields,
|
||||
req,
|
||||
result: resolvedFilterOptions,
|
||||
})
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
await Promise.all(
|
||||
field.tabs.map((tab) =>
|
||||
resolveAllFilterOptions({
|
||||
fields: tab.fields,
|
||||
req,
|
||||
result: resolvedFilterOptions,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -147,10 +147,6 @@ export const withPayload = (nextConfig = {}) => {
|
||||
toReturn.env.NEXT_BASE_PATH = nextConfig.basePath
|
||||
}
|
||||
|
||||
if (nextConfig.assetPrefix) {
|
||||
toReturn.env.NEXT_ASSET_PREFIX = nextConfig.assetPrefix
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import type * as AWS from '@aws-sdk/client-s3'
|
||||
import type { CognitoUserSession } from 'amazon-cognito-identity-js'
|
||||
|
||||
import { CognitoIdentityClient } from '@aws-sdk/client-cognito-identity'
|
||||
import * as AWS from '@aws-sdk/client-s3'
|
||||
import { fromCognitoIdentityPool } from '@aws-sdk/credential-providers'
|
||||
import type { GetStorageClient } from './refreshSession.js'
|
||||
|
||||
import { authAsCognitoUser } from './authAsCognitoUser.js'
|
||||
import { refreshSession } from './refreshSession.js'
|
||||
|
||||
export type GetStorageClient = () => Promise<{
|
||||
identityID: string
|
||||
storageClient: AWS.S3
|
||||
}>
|
||||
|
||||
let storageClient: AWS.S3 | null = null
|
||||
let session: CognitoUserSession | null = null
|
||||
let identityID: string
|
||||
export let storageClient: AWS.S3 | null = null
|
||||
export let session: CognitoUserSession | null = null
|
||||
export let identityID: string
|
||||
|
||||
export const getStorageClient: GetStorageClient = async () => {
|
||||
if (storageClient && session?.isValid()) {
|
||||
@@ -23,6 +17,8 @@ export const getStorageClient: GetStorageClient = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
;({ identityID, session, storageClient } = await refreshSession())
|
||||
|
||||
if (!process.env.PAYLOAD_CLOUD_PROJECT_ID) {
|
||||
throw new Error('PAYLOAD_CLOUD_PROJECT_ID is required')
|
||||
}
|
||||
@@ -33,34 +29,6 @@ export const getStorageClient: GetStorageClient = async () => {
|
||||
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,
|
||||
)
|
||||
|
||||
const cognitoIdentity = new CognitoIdentityClient({
|
||||
credentials: fromCognitoIdentityPool({
|
||||
clientConfig: {
|
||||
region: 'us-east-1',
|
||||
},
|
||||
identityPoolId: process.env.PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID,
|
||||
logins: {
|
||||
[`cognito-idp.us-east-1.amazonaws.com/${process.env.PAYLOAD_CLOUD_COGNITO_USER_POOL_ID}`]:
|
||||
session.getIdToken().getJwtToken(),
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const credentials = await cognitoIdentity.config.credentials()
|
||||
|
||||
// @ts-expect-error - Incorrect AWS types
|
||||
identityID = credentials.identityId
|
||||
|
||||
storageClient = new AWS.S3({
|
||||
credentials,
|
||||
region: process.env.PAYLOAD_CLOUD_BUCKET_REGION,
|
||||
})
|
||||
|
||||
return {
|
||||
identityID,
|
||||
storageClient,
|
||||
|
||||
46
packages/payload-cloud/src/utilities/refreshSession.ts
Normal file
46
packages/payload-cloud/src/utilities/refreshSession.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { CognitoIdentityClient } from '@aws-sdk/client-cognito-identity'
|
||||
import * as AWS from '@aws-sdk/client-s3'
|
||||
import { fromCognitoIdentityPool } from '@aws-sdk/credential-providers'
|
||||
|
||||
import { authAsCognitoUser } from './authAsCognitoUser.js'
|
||||
|
||||
export type GetStorageClient = () => Promise<{
|
||||
identityID: string
|
||||
storageClient: AWS.S3
|
||||
}>
|
||||
|
||||
export const refreshSession = async () => {
|
||||
const session = await authAsCognitoUser(
|
||||
process.env.PAYLOAD_CLOUD_PROJECT_ID || '',
|
||||
process.env.PAYLOAD_CLOUD_COGNITO_PASSWORD || '',
|
||||
)
|
||||
|
||||
const cognitoIdentity = new CognitoIdentityClient({
|
||||
credentials: fromCognitoIdentityPool({
|
||||
clientConfig: {
|
||||
region: 'us-east-1',
|
||||
},
|
||||
identityPoolId: process.env.PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID || '',
|
||||
logins: {
|
||||
[`cognito-idp.us-east-1.amazonaws.com/${process.env.PAYLOAD_CLOUD_COGNITO_USER_POOL_ID}`]:
|
||||
session.getIdToken().getJwtToken(),
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const credentials = await cognitoIdentity.config.credentials()
|
||||
|
||||
// @ts-expect-error - Incorrect AWS types
|
||||
const identityID = credentials.identityId
|
||||
|
||||
const storageClient = new AWS.S3({
|
||||
credentials,
|
||||
region: process.env.PAYLOAD_CLOUD_BUCKET_REGION,
|
||||
})
|
||||
|
||||
return {
|
||||
identityID,
|
||||
session,
|
||||
storageClient,
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ export type BeforeChangeRichTextHookArgs<
|
||||
|
||||
errors?: ValidationFieldError[]
|
||||
/** Only available in `beforeChange` field hooks */
|
||||
mergeLocaleActions?: (() => Promise<void>)[]
|
||||
mergeLocaleActions?: (() => Promise<void> | void)[]
|
||||
/** A string relating to which operation the field type is currently executing within. */
|
||||
operation?: 'create' | 'delete' | 'read' | 'update'
|
||||
/** The sibling data of the document before changes being applied. */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ImportMap } from '../../bin/generateImportMap/index.js'
|
||||
import type { SanitizedConfig } from '../../config/types.js'
|
||||
import type { PaginatedDocs } from '../../database/types.js'
|
||||
import type { CollectionSlug } from '../../index.js'
|
||||
import type { CollectionSlug, ColumnPreference } from '../../index.js'
|
||||
import type { PayloadRequest, Sort, Where } from '../../types/index.js'
|
||||
|
||||
export type DefaultServerFunctionArgs = {
|
||||
@@ -50,7 +50,7 @@ export type ListQuery = {
|
||||
|
||||
export type BuildTableStateArgs = {
|
||||
collectionSlug: string | string[]
|
||||
columns?: { accessor: string; active: boolean }[]
|
||||
columns?: ColumnPreference[]
|
||||
docs?: PaginatedDocs['docs']
|
||||
enableRowSelections?: boolean
|
||||
parent?: {
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
// @ts-strict-ignore
|
||||
import type { AuthStrategyFunctionArgs, AuthStrategyResult } from './index.js'
|
||||
|
||||
export const executeAuthStrategies = async (
|
||||
args: AuthStrategyFunctionArgs,
|
||||
): Promise<AuthStrategyResult> => {
|
||||
return args.payload.authStrategies.reduce(
|
||||
async (accumulatorPromise, strategy) => {
|
||||
const result: AuthStrategyResult = await accumulatorPromise
|
||||
if (!result.user) {
|
||||
// add the configured AuthStrategy `name` to the strategy function args
|
||||
args.strategyName = strategy.name
|
||||
if (!args.payload.authStrategies?.length) {
|
||||
return { user: null }
|
||||
}
|
||||
|
||||
return strategy.authenticate(args)
|
||||
}
|
||||
for (const strategy of args.payload.authStrategies) {
|
||||
// add the configured AuthStrategy `name` to the strategy function args
|
||||
args.strategyName = strategy.name
|
||||
|
||||
const result = await strategy.authenticate(args)
|
||||
if (result.user) {
|
||||
return result
|
||||
},
|
||||
Promise.resolve({ user: null }),
|
||||
)
|
||||
}
|
||||
}
|
||||
return { user: null }
|
||||
}
|
||||
|
||||
@@ -64,18 +64,18 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'forgotPassword',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'forgotPassword',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
@@ -190,10 +190,11 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterForgotPassword - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
await hook({ args, collection: args.collection?.config, context: req.context })
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterForgotPassword?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterForgotPassword) {
|
||||
await hook({ args, collection: args.collection?.config, context: req.context })
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -51,18 +51,18 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'login',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'login',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
@@ -227,17 +227,17 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeLogin - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeLogin.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
user,
|
||||
})) || user
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeLogin?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeLogin) {
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
user,
|
||||
})) || user
|
||||
}
|
||||
}
|
||||
|
||||
const { exp, token } = await jwtSign({
|
||||
fieldsToSign,
|
||||
@@ -251,18 +251,18 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterLogin - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterLogin.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
token,
|
||||
user,
|
||||
})) || user
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterLogin?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterLogin) {
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
token,
|
||||
user,
|
||||
})) || user
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -286,17 +286,17 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
doc: user,
|
||||
req,
|
||||
})) || user
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
doc: user,
|
||||
req,
|
||||
})) || user
|
||||
}
|
||||
}
|
||||
|
||||
let result: { user: DataFromCollectionSlug<TSlug> } & Result = {
|
||||
exp,
|
||||
|
||||
@@ -25,16 +25,16 @@ export const logoutOperation = async (incomingArgs: Arguments): Promise<boolean>
|
||||
throw new APIError('Incorrect collection', httpStatus.FORBIDDEN)
|
||||
}
|
||||
|
||||
await collectionConfig.hooks.afterLogout.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterLogout?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterLogout) {
|
||||
args =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -86,17 +86,17 @@ export const meOperation = async (args: Arguments): Promise<MeOperationResult> =
|
||||
// After Me - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collection.config.hooks.afterMe.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
response: result,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collection.config.hooks?.afterMe?.length) {
|
||||
for (const hook of collection.config.hooks.afterMe) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
response: result,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -35,10 +35,8 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
@@ -47,9 +45,8 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
operation: 'refresh',
|
||||
req: args.req,
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Refresh
|
||||
@@ -122,18 +119,18 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
// After Refresh - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRefresh.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
exp: result.exp,
|
||||
req: args.req,
|
||||
token: result.refreshedToken,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRefresh?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRefresh) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
exp: result.exp,
|
||||
req: args.req,
|
||||
token: result.refreshedToken,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -91,17 +91,17 @@ export const resetPasswordOperation = async (args: Arguments): Promise<Result> =
|
||||
// beforeValidate - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
data: user,
|
||||
operation: 'update',
|
||||
req,
|
||||
})
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeValidate?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeValidate) {
|
||||
await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
data: user,
|
||||
operation: 'update',
|
||||
req,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Update new password
|
||||
|
||||
@@ -54,6 +54,8 @@ async function autoLogin({
|
||||
await payload.find({
|
||||
collection: collection.config.slug,
|
||||
depth: isGraphQL ? 0 : collection.config.auth.depth,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
where,
|
||||
})
|
||||
).docs[0]
|
||||
|
||||
@@ -76,8 +76,18 @@ export const bin = async () => {
|
||||
|
||||
if (userBinScript) {
|
||||
try {
|
||||
const script: BinScript = await import(pathToFileURL(userBinScript.scriptPath).toString())
|
||||
await script(config)
|
||||
const module = await import(pathToFileURL(userBinScript.scriptPath).toString())
|
||||
|
||||
if (!module.script || typeof module.script !== 'function') {
|
||||
console.error(
|
||||
`Could not find "script" function export for script ${userBinScript.key} in ${userBinScript.scriptPath}`,
|
||||
)
|
||||
} else {
|
||||
await module.script(config).catch((err: unknown) => {
|
||||
console.log(`Script ${userBinScript.key} failed, details:`)
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Could not find associated bin script for the ${userBinScript.key} command`)
|
||||
console.error(err)
|
||||
|
||||
@@ -44,7 +44,9 @@ const batchAndLoadDocs =
|
||||
*
|
||||
**/
|
||||
|
||||
const batchByFindArgs = keys.reduce((batches, key) => {
|
||||
const batchByFindArgs = {}
|
||||
|
||||
for (const key of keys) {
|
||||
const [
|
||||
transactionID,
|
||||
collection,
|
||||
@@ -77,27 +79,16 @@ const batchAndLoadDocs =
|
||||
const batchKey = JSON.stringify(batchKeyArray)
|
||||
|
||||
const idType = payload.collections?.[collection].customIDType || payload.db.defaultIDType
|
||||
|
||||
let sanitizedID: number | string = id
|
||||
|
||||
if (idType === 'number') {
|
||||
sanitizedID = parseFloat(id)
|
||||
}
|
||||
const sanitizedID = idType === 'number' ? parseFloat(id) : id
|
||||
|
||||
if (isValidID(sanitizedID, idType)) {
|
||||
return {
|
||||
...batches,
|
||||
[batchKey]: [...(batches[batchKey] || []), sanitizedID],
|
||||
}
|
||||
batchByFindArgs[batchKey] = [...(batchByFindArgs[batchKey] || []), sanitizedID]
|
||||
}
|
||||
return batches
|
||||
}, {})
|
||||
}
|
||||
|
||||
// Run find requests one after another, so as to not hang transactions
|
||||
|
||||
await Object.entries(batchByFindArgs).reduce(async (priorFind, [batchKey, ids]) => {
|
||||
await priorFind
|
||||
|
||||
for (const [batchKey, ids] of Object.entries(batchByFindArgs)) {
|
||||
const [
|
||||
transactionID,
|
||||
collection,
|
||||
@@ -137,8 +128,7 @@ const batchAndLoadDocs =
|
||||
|
||||
// For each returned doc, find index in original keys
|
||||
// Inject doc within docs array if index exists
|
||||
|
||||
result.docs.forEach((doc) => {
|
||||
for (const doc of result.docs) {
|
||||
const docKey = createDataloaderCacheKey({
|
||||
collectionSlug: collection,
|
||||
currentDepth,
|
||||
@@ -158,8 +148,8 @@ const batchAndLoadDocs =
|
||||
if (docsIndex > -1) {
|
||||
docs[docsIndex] = doc
|
||||
}
|
||||
})
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
// Return docs array,
|
||||
// which has now been injected with all fetched docs
|
||||
|
||||
@@ -28,18 +28,18 @@ export const countOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'count',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'count',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
|
||||
@@ -28,18 +28,18 @@ export const countVersionsOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'countVersions',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'countVersions',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
|
||||
@@ -78,10 +78,8 @@ export const createOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
@@ -90,9 +88,8 @@ export const createOperation = async <
|
||||
operation: 'create',
|
||||
req: args.req,
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
autosave = false,
|
||||
@@ -183,10 +180,8 @@ export const createOperation = async <
|
||||
// beforeValidate - Collections
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeValidate.reduce(
|
||||
async (priorHook: BeforeValidateHook | Promise<void>, hook: BeforeValidateHook) => {
|
||||
await priorHook
|
||||
|
||||
if (collectionConfig.hooks.beforeValidate?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeValidate) {
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -196,27 +191,26 @@ export const createOperation = async <
|
||||
originalDoc: duplicatedFromDoc,
|
||||
req,
|
||||
})) || data
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'create',
|
||||
originalDoc: duplicatedFromDoc,
|
||||
req,
|
||||
})) || data
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeChange) {
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'create',
|
||||
originalDoc: duplicatedFromDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
@@ -332,17 +326,17 @@ export const createOperation = async <
|
||||
// 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())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -363,10 +357,8 @@ export const createOperation = async <
|
||||
// afterChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterChange.reduce(
|
||||
async (priorHook: AfterChangeHook | Promise<void>, hook: AfterChangeHook) => {
|
||||
await priorHook
|
||||
|
||||
if (collectionConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -376,9 +368,8 @@ export const createOperation = async <
|
||||
previousDoc: {},
|
||||
req: args.req,
|
||||
})) || result
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -54,10 +54,8 @@ export const deleteOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
@@ -66,9 +64,8 @@ export const deleteOperation = async <
|
||||
operation: 'delete',
|
||||
req: args.req,
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
@@ -147,16 +144,16 @@ export const deleteOperation = async <
|
||||
// beforeDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeDelete.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
return hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeDelete?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeDelete) {
|
||||
await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await deleteAssociatedFiles({
|
||||
collectionConfig,
|
||||
@@ -229,34 +226,34 @@ export const deleteOperation = async <
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result || doc,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result || doc,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterDelete.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterDelete?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterDelete) {
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 8. Return results
|
||||
|
||||
@@ -48,10 +48,8 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
@@ -60,9 +58,8 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
operation: 'delete',
|
||||
req: args.req,
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
id,
|
||||
@@ -95,16 +92,16 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
// beforeDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeDelete.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
return hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeDelete?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeDelete) {
|
||||
await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Retrieve document
|
||||
@@ -215,34 +212,34 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
// 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())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterDelete.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterDelete?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterDelete) {
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -63,18 +63,18 @@ export const findOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
@@ -257,9 +257,7 @@ export const findOperation = async <
|
||||
result.docs.map(async (doc) => {
|
||||
let docRef = doc
|
||||
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of collectionConfig.hooks.beforeRead) {
|
||||
docRef =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -268,7 +266,7 @@ export const findOperation = async <
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || docRef
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
@@ -310,9 +308,7 @@ export const findOperation = async <
|
||||
result.docs.map(async (doc) => {
|
||||
let docRef = doc
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
docRef =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -322,7 +318,7 @@ export const findOperation = async <
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || doc
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
|
||||
@@ -54,18 +54,18 @@ export const findByIDOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
id,
|
||||
@@ -221,18 +221,18 @@ export const findByIDOperation = async <
|
||||
// beforeRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -259,18 +259,18 @@ export const findByIDOperation = async <
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -101,18 +101,18 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
|
||||
// beforeRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeRead) {
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || result.version
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -139,18 +139,18 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || result.version
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
|
||||
@@ -96,18 +96,19 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
|
||||
if (!docRef.version) {
|
||||
;(docRef as any).version = {}
|
||||
}
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
docRef.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: docRef.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || docRef.version
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeRead) {
|
||||
docRef.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: docRef.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || docRef.version
|
||||
}
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
@@ -147,9 +148,7 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
|
||||
result.docs.map(async (doc) => {
|
||||
const docRef = doc
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
docRef.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -159,7 +158,7 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || doc.version
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
|
||||
@@ -165,17 +165,17 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
|
||||
// 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())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -196,19 +196,19 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
|
||||
// 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: prevDocWithLocales,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
previousDoc: prevDocWithLocales,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -62,18 +62,18 @@ export const updateOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
|
||||
@@ -64,18 +64,18 @@ export const updateByIDOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
if (args.publishSpecificLocale) {
|
||||
args.req.locale = args.publishSpecificLocale
|
||||
|
||||
@@ -171,19 +171,19 @@ export const updateDocument = async <
|
||||
// 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())
|
||||
if (collectionConfig.hooks?.beforeValidate?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeValidate) {
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Write files to local storage
|
||||
@@ -197,19 +197,19 @@ export const updateDocument = async <
|
||||
// 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())
|
||||
if (collectionConfig.hooks?.beforeChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeChange) {
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
@@ -338,17 +338,17 @@ export const updateDocument = async <
|
||||
// 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())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -369,19 +369,19 @@ export const updateDocument = async <
|
||||
// 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())
|
||||
if (collectionConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
return result as TransformCollectionWithSelect<TSlug, TSelect>
|
||||
}
|
||||
|
||||
@@ -125,10 +125,8 @@ export const buildAfterOperation = async <
|
||||
|
||||
let newResult = result as OperationResult<TOperationGeneric, O>
|
||||
|
||||
await args.collection.config.hooks.afterOperation.reduce(
|
||||
async (priorHook, hook: AfterOperationHook<TOperationGeneric>) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks?.afterOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.afterOperation) {
|
||||
const hookResult = await hook({
|
||||
args,
|
||||
collection,
|
||||
@@ -140,9 +138,8 @@ export const buildAfterOperation = async <
|
||||
if (hookResult !== undefined) {
|
||||
newResult = hookResult as OperationResult<TOperationGeneric, O>
|
||||
}
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return newResult
|
||||
}
|
||||
|
||||
@@ -9,11 +9,10 @@ import { sanitizeConfig } from './sanitize.js'
|
||||
*/
|
||||
export async function buildConfig(config: Config): Promise<SanitizedConfig> {
|
||||
if (Array.isArray(config.plugins)) {
|
||||
const configAfterPlugins = await config.plugins.reduce(async (acc, plugin) => {
|
||||
const configAfterPlugin = await acc
|
||||
return plugin(configAfterPlugin)
|
||||
}, Promise.resolve(config))
|
||||
|
||||
let configAfterPlugins = config
|
||||
for (const plugin of config.plugins) {
|
||||
configAfterPlugins = await plugin(configAfterPlugins)
|
||||
}
|
||||
return await sanitizeConfig(configAfterPlugins)
|
||||
}
|
||||
|
||||
|
||||
@@ -179,10 +179,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
|
||||
}))
|
||||
} else {
|
||||
// is Locale[], so convert to string[] for localeCodes
|
||||
config.localization.localeCodes = config.localization.locales.reduce((locales, locale) => {
|
||||
locales.push(locale.code)
|
||||
return locales
|
||||
}, [] as string[])
|
||||
config.localization.localeCodes = config.localization.locales.map((locale) => locale.code)
|
||||
|
||||
config.localization.locales = (
|
||||
config.localization as LocalizationConfigWithLabels
|
||||
|
||||
@@ -75,10 +75,8 @@ export const promise = async ({
|
||||
if (fieldAffectsData(field)) {
|
||||
// Execute hooks
|
||||
if (field.hooks?.afterChange) {
|
||||
await field.hooks.afterChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of field.hooks.afterChange) {
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -102,7 +100,7 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,17 +240,15 @@ export const promise = async ({
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
|
||||
if (typeof field?.editor === 'function') {
|
||||
if (typeof field.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
const editor: RichTextAdapter = field.editor
|
||||
|
||||
if (editor?.hooks?.afterChange?.length) {
|
||||
await editor.hooks.afterChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of editor.hooks.afterChange) {
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
@@ -275,7 +271,7 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -237,18 +237,17 @@ export const promise = async ({
|
||||
if (fieldAffectsData(field)) {
|
||||
// Execute hooks
|
||||
if (triggerHooks && field.hooks?.afterRead) {
|
||||
await field.hooks.afterRead.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of field.hooks.afterRead) {
|
||||
const shouldRunHookOnAllLocales =
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
(locale === 'all' || !flattenLocales) &&
|
||||
typeof siblingDoc[field.name] === 'object'
|
||||
|
||||
if (shouldRunHookOnAllLocales) {
|
||||
const hookPromises = Object.entries(siblingDoc[field.name]).map(([locale, value]) =>
|
||||
(async () => {
|
||||
const hookedValue = await currentHook({
|
||||
const localesAndValues = Object.entries(siblingDoc[field.name])
|
||||
await Promise.all(
|
||||
localesAndValues.map(async ([localeKey, value]) => {
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -273,14 +272,12 @@ export const promise = async ({
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name][locale] = hookedValue
|
||||
siblingDoc[field.name][localeKey] = hookedValue
|
||||
}
|
||||
})(),
|
||||
}),
|
||||
)
|
||||
|
||||
await Promise.all(hookPromises)
|
||||
} else {
|
||||
const hookedValue = await currentHook({
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -308,7 +305,7 @@ export const promise = async ({
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
// Execute access control
|
||||
@@ -677,18 +674,18 @@ export const promise = async ({
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.afterRead?.length) {
|
||||
await editor.hooks.afterRead.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of editor.hooks.afterRead) {
|
||||
const shouldRunHookOnAllLocales =
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
(locale === 'all' || !flattenLocales) &&
|
||||
typeof siblingDoc[field.name] === 'object'
|
||||
|
||||
if (shouldRunHookOnAllLocales) {
|
||||
const hookPromises = Object.entries(siblingDoc[field.name]).map(([locale, value]) =>
|
||||
(async () => {
|
||||
const hookedValue = await currentHook({
|
||||
const localesAndValues = Object.entries(siblingDoc[field.name])
|
||||
|
||||
await Promise.all(
|
||||
localesAndValues.map(async ([locale, value]) => {
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
@@ -722,12 +719,10 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name][locale] = hookedValue
|
||||
}
|
||||
})(),
|
||||
}),
|
||||
)
|
||||
|
||||
await Promise.all(hookPromises)
|
||||
} else {
|
||||
const hookedValue = await currentHook({
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
@@ -762,7 +757,7 @@ export const promise = async ({
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -81,10 +81,9 @@ export const beforeChange = async <T extends JsonObject>({
|
||||
)
|
||||
}
|
||||
|
||||
await mergeLocaleActions.reduce(async (priorAction, action) => {
|
||||
await priorAction
|
||||
for (const action of mergeLocaleActions) {
|
||||
await action()
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ type Args = {
|
||||
fieldIndex: number
|
||||
global: null | SanitizedGlobalConfig
|
||||
id?: number | string
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
mergeLocaleActions: (() => Promise<void> | void)[]
|
||||
operation: Operation
|
||||
parentIndexPath: string
|
||||
parentIsLocalized: boolean
|
||||
@@ -109,10 +109,8 @@ export const promise = async ({
|
||||
|
||||
// Execute hooks
|
||||
if (field.hooks?.beforeChange) {
|
||||
await field.hooks.beforeChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of field.hooks.beforeChange) {
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -136,7 +134,7 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
// Validate
|
||||
@@ -193,28 +191,20 @@ export const promise = async ({
|
||||
|
||||
// Push merge locale action if applicable
|
||||
if (localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
mergeLocaleActions.push(async () => {
|
||||
const localeData = await localization.localeCodes.reduce(
|
||||
async (localizedValuesPromise: Promise<JsonObject>, locale) => {
|
||||
const localizedValues = await localizedValuesPromise
|
||||
const fieldValue =
|
||||
locale === req.locale
|
||||
? siblingData[field.name]
|
||||
: siblingDocWithLocales?.[field.name]?.[locale]
|
||||
mergeLocaleActions.push(() => {
|
||||
const localeData = {}
|
||||
|
||||
// const result = await localizedValues
|
||||
// update locale value if it's not undefined
|
||||
if (typeof fieldValue !== 'undefined') {
|
||||
return {
|
||||
...localizedValues,
|
||||
[locale]: fieldValue,
|
||||
}
|
||||
}
|
||||
for (const locale of localization.localeCodes) {
|
||||
const fieldValue =
|
||||
locale === req.locale
|
||||
? siblingData[field.name]
|
||||
: siblingDocWithLocales?.[field.name]?.[locale]
|
||||
|
||||
return localizedValuesPromise
|
||||
},
|
||||
Promise.resolve({}),
|
||||
)
|
||||
// update locale value if it's not undefined
|
||||
if (typeof fieldValue !== 'undefined') {
|
||||
localeData[locale] = fieldValue
|
||||
}
|
||||
}
|
||||
|
||||
// If there are locales with data, set the data
|
||||
if (Object.keys(localeData).length > 0) {
|
||||
@@ -424,10 +414,8 @@ export const promise = async ({
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.beforeChange?.length) {
|
||||
await editor.hooks.beforeChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of editor.hooks.beforeChange) {
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
@@ -454,7 +442,7 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
@@ -28,7 +28,7 @@ type Args = {
|
||||
fields: (Field | TabAsField)[]
|
||||
global: null | SanitizedGlobalConfig
|
||||
id?: number | string
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
mergeLocaleActions: (() => Promise<void> | void)[]
|
||||
operation: Operation
|
||||
parentIndexPath: string
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { Block, Field, FieldHookArgs, TabAsField } from '../../config/types
|
||||
|
||||
import { fieldAffectsData, fieldShouldBeLocalized } from '../../config/types.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { runBeforeDuplicateHooks } from './runHook.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args<T> = {
|
||||
@@ -68,42 +67,37 @@ export const promise = async <T>({
|
||||
// Run field beforeDuplicate hooks
|
||||
if (Array.isArray(field.hooks?.beforeDuplicate)) {
|
||||
if (fieldIsLocalized) {
|
||||
const localeData = await localization.localeCodes.reduce(
|
||||
async (localizedValuesPromise: Promise<JsonObject>, locale) => {
|
||||
const localizedValues = await localizedValuesPromise
|
||||
const localeData: JsonObject = {}
|
||||
|
||||
const beforeDuplicateArgs: FieldHookArgs = {
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
data: doc,
|
||||
field,
|
||||
global: undefined,
|
||||
indexPath: indexPathSegments,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name]?.[locale],
|
||||
req,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData: siblingDoc,
|
||||
siblingDocWithLocales: siblingDoc,
|
||||
siblingFields,
|
||||
value: siblingDoc[field.name]?.[locale],
|
||||
}
|
||||
for (const locale of localization.localeCodes) {
|
||||
const beforeDuplicateArgs: FieldHookArgs = {
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
data: doc,
|
||||
field,
|
||||
global: undefined,
|
||||
indexPath: indexPathSegments,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name]?.[locale],
|
||||
req,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData: siblingDoc,
|
||||
siblingDocWithLocales: siblingDoc,
|
||||
siblingFields,
|
||||
value: siblingDoc[field.name]?.[locale],
|
||||
}
|
||||
|
||||
const hookResult = await runBeforeDuplicateHooks(beforeDuplicateArgs)
|
||||
let hookResult
|
||||
for (const hook of field.hooks.beforeDuplicate) {
|
||||
hookResult = await hook(beforeDuplicateArgs)
|
||||
}
|
||||
|
||||
if (typeof hookResult !== 'undefined') {
|
||||
return {
|
||||
...localizedValues,
|
||||
[locale]: hookResult,
|
||||
}
|
||||
}
|
||||
|
||||
return localizedValuesPromise
|
||||
},
|
||||
Promise.resolve({}),
|
||||
)
|
||||
if (typeof hookResult !== 'undefined') {
|
||||
localeData[locale] = hookResult
|
||||
}
|
||||
}
|
||||
|
||||
siblingDoc[field.name] = localeData
|
||||
} else {
|
||||
@@ -126,7 +120,11 @@ export const promise = async <T>({
|
||||
value: siblingDoc[field.name],
|
||||
}
|
||||
|
||||
const hookResult = await runBeforeDuplicateHooks(beforeDuplicateArgs)
|
||||
let hookResult
|
||||
for (const hook of field.hooks.beforeDuplicate) {
|
||||
hookResult = await hook(beforeDuplicateArgs)
|
||||
}
|
||||
|
||||
if (typeof hookResult !== 'undefined') {
|
||||
siblingDoc[field.name] = hookResult
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// @ts-strict-ignore
|
||||
import type { FieldHookArgs } from '../../config/types.js'
|
||||
|
||||
export const runBeforeDuplicateHooks = async (args: FieldHookArgs) =>
|
||||
await args.field.hooks.beforeDuplicate.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
return await currentHook(args)
|
||||
}, Promise.resolve())
|
||||
@@ -276,10 +276,8 @@ export const promise = async <T>({
|
||||
|
||||
// Execute hooks
|
||||
if (field.hooks?.beforeValidate) {
|
||||
await field.hooks.beforeValidate.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of field.hooks.beforeValidate) {
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -303,7 +301,7 @@ export const promise = async <T>({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
// Execute access control
|
||||
@@ -493,10 +491,8 @@ export const promise = async <T>({
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.beforeValidate?.length) {
|
||||
await editor.hooks.beforeValidate.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of editor.hooks.beforeValidate) {
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
@@ -519,7 +515,7 @@ export const promise = async <T>({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -133,17 +133,17 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
|
||||
// Execute before global hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of globalConfig.hooks.beforeRead) {
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Execute globalType field if not selected
|
||||
@@ -182,17 +182,17 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
|
||||
// Execute after global hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
|
||||
@@ -102,17 +102,17 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
|
||||
// beforeRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of globalConfig.hooks.beforeRead) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result.version
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -139,18 +139,18 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
query: findGlobalVersionsArgs.where,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
result.version =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
query: findGlobalVersionsArgs.where,
|
||||
req,
|
||||
})) || result.version
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -126,15 +126,12 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = {
|
||||
...result,
|
||||
docs: await Promise.all(
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
result.docs = await Promise.all(
|
||||
result.docs.map(async (doc) => {
|
||||
const docRef = doc
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
docRef.version =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
@@ -144,11 +141,11 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || doc.version
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -143,17 +143,17 @@ export const restoreVersionOperation = async <T extends TypeWithVersion<T> = any
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -174,18 +174,18 @@ export const restoreVersionOperation = async <T extends TypeWithVersion<T> = any
|
||||
// afterChange - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of globalConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCommit) {
|
||||
await commitTransaction(req)
|
||||
|
||||
@@ -168,35 +168,35 @@ export const updateOperation = async <
|
||||
// beforeValidate - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.beforeValidate?.length) {
|
||||
for (const hook of globalConfig.hooks.beforeValidate) {
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.beforeChange?.length) {
|
||||
for (const hook of globalConfig.hooks.beforeChange) {
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
@@ -326,17 +326,17 @@ export const updateOperation = async <
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -357,18 +357,18 @@ export const updateOperation = async <
|
||||
// afterChange - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of globalConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
|
||||
@@ -922,11 +922,13 @@ export const getPayload = async (
|
||||
) {
|
||||
try {
|
||||
const port = process.env.PORT || '3000'
|
||||
const basePath = process.env.NEXT_BASE_PATH || ''
|
||||
const assetPrefix = process.env.NEXT_ASSET_PREFIX || ''
|
||||
|
||||
const path = '/_next/webpack-hmr'
|
||||
// The __NEXT_ASSET_PREFIX env variable is set for both assetPrefix and basePath (tested in Next.js 15.1.6)
|
||||
const prefix = process.env.__NEXT_ASSET_PREFIX ?? ''
|
||||
|
||||
cached.ws = new WebSocket(
|
||||
`ws://localhost:${port}${basePath}${assetPrefix}/_next/webpack-hmr`,
|
||||
process.env.PAYLOAD_HMR_URL_OVERRIDE ?? `ws://localhost:${port}${prefix}${path}`,
|
||||
)
|
||||
|
||||
cached.ws.onmessage = (event) => {
|
||||
@@ -1372,6 +1374,7 @@ export { restoreVersionOperation as restoreVersionOperationGlobal } from './glob
|
||||
export { updateOperation as updateOperationGlobal } from './globals/operations/update.js'
|
||||
export type {
|
||||
CollapsedPreferences,
|
||||
ColumnPreference,
|
||||
DocumentPreferences,
|
||||
FieldsPreferences,
|
||||
InsideFieldsPreferences,
|
||||
|
||||
19
packages/payload/src/preferences/migrateColumns.ts
Normal file
19
packages/payload/src/preferences/migrateColumns.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @todo remove this function and subsequent hooks in v4
|
||||
* They are used to transform the old shape of `columnPreferences` to new shape
|
||||
* i.e. ({ accessor: string, active: boolean })[] to ({ [accessor: string]: boolean })[]
|
||||
* In v4 can we use the new shape directly
|
||||
*/
|
||||
export const migrateColumns = (value: Record<string, any>) => {
|
||||
if (value && typeof value === 'object' && 'columns' in value && Array.isArray(value.columns)) {
|
||||
value.columns = value.columns.map((col) => {
|
||||
if ('accessor' in col) {
|
||||
return { [col.accessor]: col.active }
|
||||
}
|
||||
|
||||
return col
|
||||
})
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { CollectionConfig } from '../collections/config/types.js'
|
||||
import type { Access, Config } from '../config/types.js'
|
||||
|
||||
import { migrateColumns } from './migrateColumns.js'
|
||||
import { deleteHandler } from './requestHandlers/delete.js'
|
||||
import { findByIDHandler } from './requestHandlers/findOne.js'
|
||||
import { updateHandler } from './requestHandlers/update.js'
|
||||
@@ -76,6 +77,14 @@ const getPreferencesCollection = (config: Config): CollectionConfig => ({
|
||||
{
|
||||
name: 'value',
|
||||
type: 'json',
|
||||
/**
|
||||
* @todo remove these hooks in v4
|
||||
* See `migrateColumns` for more information
|
||||
*/
|
||||
hooks: {
|
||||
afterRead: [({ value }) => migrateColumns(value)],
|
||||
beforeValidate: [({ value }) => migrateColumns(value)],
|
||||
},
|
||||
validate: (value) => {
|
||||
if (value) {
|
||||
try {
|
||||
|
||||
@@ -28,8 +28,12 @@ export type DocumentPreferences = {
|
||||
fields: FieldsPreferences
|
||||
}
|
||||
|
||||
export type ColumnPreference = {
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
export type ListPreferences = {
|
||||
columns?: { accessor: string; active: boolean }[]
|
||||
columns?: ColumnPreference[]
|
||||
limit?: number
|
||||
sort?: string
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export const validOperators = [
|
||||
'less_than',
|
||||
'less_than_equal',
|
||||
'like',
|
||||
'not_like',
|
||||
'within',
|
||||
'intersects',
|
||||
'near',
|
||||
|
||||
@@ -19,7 +19,8 @@ export const addDataAndFileToRequest: AddDataAndFileToRequest = async (req) => {
|
||||
if (contentType === 'application/json') {
|
||||
let data = {}
|
||||
try {
|
||||
data = await req.json()
|
||||
const text = await req.text()
|
||||
data = text ? JSON.parse(text) : {}
|
||||
} catch (error) {
|
||||
req.payload.logger.error(error)
|
||||
} finally {
|
||||
|
||||
@@ -15,7 +15,6 @@ import type { SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||
import { MissingEditorProp } from '../errors/MissingEditorProp.js'
|
||||
import { fieldAffectsData } from '../fields/config/types.js'
|
||||
import { generateJobsJSONSchemas } from '../queues/config/generateJobsJSONSchemas.js'
|
||||
import { deepCopyObject } from './deepCopyObject.js'
|
||||
import { toWords } from './formatLabels.js'
|
||||
import { getCollectionIDFieldTypes } from './getCollectionIDFieldTypes.js'
|
||||
|
||||
@@ -719,7 +718,7 @@ export function fieldsToJSONSchema(
|
||||
// This function is part of the public API and is exported through payload/utilities
|
||||
export function entityToJSONSchema(
|
||||
config: SanitizedConfig,
|
||||
incomingEntity: SanitizedCollectionConfig | SanitizedGlobalConfig,
|
||||
entity: SanitizedCollectionConfig | SanitizedGlobalConfig,
|
||||
interfaceNameDefinitions: Map<string, JSONSchema4>,
|
||||
defaultIDType: 'number' | 'text',
|
||||
collectionIDFieldTypes?: { [key: string]: 'number' | 'string' },
|
||||
@@ -729,25 +728,30 @@ export function entityToJSONSchema(
|
||||
collectionIDFieldTypes = getCollectionIDFieldTypes({ config, defaultIDType })
|
||||
}
|
||||
|
||||
const entity: SanitizedCollectionConfig | SanitizedGlobalConfig = deepCopyObject(incomingEntity)
|
||||
const title = entity.typescript?.interface
|
||||
? entity.typescript.interface
|
||||
: singular(toWords(entity.slug, true))
|
||||
|
||||
let mutableFields = [...entity.flattenedFields]
|
||||
|
||||
const idField: FieldAffectingData = { name: 'id', type: defaultIDType as 'text', required: true }
|
||||
const customIdField = entity.flattenedFields.find(
|
||||
(field) => field.name === 'id',
|
||||
) as FieldAffectingData
|
||||
const customIdField = mutableFields.find((field) => field.name === 'id') as FieldAffectingData
|
||||
|
||||
if (customIdField && customIdField.type !== 'group' && customIdField.type !== 'tab') {
|
||||
customIdField.required = true
|
||||
mutableFields = mutableFields.map((field) => {
|
||||
if (field === customIdField) {
|
||||
return { ...field, required: true }
|
||||
}
|
||||
|
||||
return field
|
||||
})
|
||||
} else {
|
||||
entity.flattenedFields.unshift(idField)
|
||||
mutableFields.unshift(idField)
|
||||
}
|
||||
|
||||
// mark timestamp fields required
|
||||
if ('timestamps' in entity && entity.timestamps !== false) {
|
||||
entity.flattenedFields = entity.flattenedFields.map((field) => {
|
||||
mutableFields = mutableFields.map((field) => {
|
||||
if (field.name === 'createdAt' || field.name === 'updatedAt') {
|
||||
return {
|
||||
...field,
|
||||
@@ -765,7 +769,7 @@ export function entityToJSONSchema(
|
||||
(typeof entity.auth?.disableLocalStrategy === 'object' &&
|
||||
entity.auth.disableLocalStrategy.enableFields))
|
||||
) {
|
||||
entity.flattenedFields.push({
|
||||
mutableFields.push({
|
||||
name: 'password',
|
||||
type: 'text',
|
||||
})
|
||||
@@ -777,7 +781,7 @@ export function entityToJSONSchema(
|
||||
title,
|
||||
...fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
entity.flattenedFields,
|
||||
mutableFields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
i18n,
|
||||
@@ -1152,40 +1156,42 @@ export function configToJSONSchema(
|
||||
)
|
||||
: {}
|
||||
|
||||
const blocksDefinition: JSONSchema4 = {
|
||||
const blocksDefinition: JSONSchema4 | undefined = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
required: [],
|
||||
}
|
||||
for (const block of config.blocks) {
|
||||
const blockFieldSchemas = fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
block.flattenedFields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
i18n,
|
||||
)
|
||||
if (config?.blocks?.length) {
|
||||
for (const block of config.blocks) {
|
||||
const blockFieldSchemas = fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
block.flattenedFields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
i18n,
|
||||
)
|
||||
|
||||
const blockSchema: JSONSchema4 = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
...blockFieldSchemas.properties,
|
||||
blockType: {
|
||||
const: block.slug,
|
||||
const blockSchema: JSONSchema4 = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
...blockFieldSchemas.properties,
|
||||
blockType: {
|
||||
const: block.slug,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['blockType', ...blockFieldSchemas.required],
|
||||
}
|
||||
required: ['blockType', ...blockFieldSchemas.required],
|
||||
}
|
||||
|
||||
const interfaceName = block.interfaceName ?? block.slug
|
||||
interfaceNameDefinitions.set(interfaceName, blockSchema)
|
||||
const interfaceName = block.interfaceName ?? block.slug
|
||||
interfaceNameDefinitions.set(interfaceName, blockSchema)
|
||||
|
||||
blocksDefinition.properties[block.slug] = {
|
||||
$ref: `#/definitions/${interfaceName}`,
|
||||
blocksDefinition.properties[block.slug] = {
|
||||
$ref: `#/definitions/${interfaceName}`,
|
||||
}
|
||||
;(blocksDefinition.required as string[]).push(block.slug)
|
||||
}
|
||||
;(blocksDefinition.required as string[]).push(block.slug)
|
||||
}
|
||||
|
||||
let jsonSchema: JSONSchema4 = {
|
||||
@@ -1225,6 +1231,7 @@ export function configToJSONSchema(
|
||||
],
|
||||
title: 'Config',
|
||||
}
|
||||
|
||||
if (jobsSchemas.definitions?.size) {
|
||||
for (const [key, value] of jobsSchemas.definitions) {
|
||||
jsonSchema.definitions[key] = value
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { TenantField } from '../components/TenantField/index.client.js'
|
||||
export { TenantSelector } from '../components/TenantSelector/index.js'
|
||||
export { useTenantSelection } from '../providers/TenantSelectionProvider/index.client.js'
|
||||
|
||||
@@ -115,7 +115,13 @@ export const afterTenantDelete =
|
||||
id: user.id,
|
||||
collection: usersSlug,
|
||||
data: {
|
||||
tenants: (user.tenants || []).filter(({ tenant: tenantID }) => tenantID !== id),
|
||||
[usersTenantsArrayFieldName]: (user[usersTenantsArrayFieldName] || []).filter(
|
||||
(row: Record<string, string>) => {
|
||||
if (row[usersTenantsArrayTenantFieldName]) {
|
||||
return row[usersTenantsArrayTenantFieldName] !== id
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -92,6 +92,8 @@ export const multiTenantPlugin =
|
||||
addCollectionAccess({
|
||||
collection: adminUsersCollection,
|
||||
fieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
|
||||
@@ -130,6 +132,8 @@ export const multiTenantPlugin =
|
||||
addCollectionAccess({
|
||||
collection,
|
||||
fieldName: 'id',
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
}
|
||||
@@ -205,6 +209,8 @@ export const multiTenantPlugin =
|
||||
addCollectionAccess({
|
||||
collection,
|
||||
fieldName: tenantFieldName,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,9 +9,26 @@ import React, { createContext } from 'react'
|
||||
import { SELECT_ALL } from '../../constants.js'
|
||||
|
||||
type ContextType = {
|
||||
/**
|
||||
* Array of options to select from
|
||||
*/
|
||||
options: OptionObject[]
|
||||
/**
|
||||
* The currently selected tenant ID
|
||||
*/
|
||||
selectedTenantID: number | string | undefined
|
||||
/**
|
||||
* Prevents a refresh when the tenant is changed
|
||||
*
|
||||
* If not switching tenants while viewing a "global", set to true
|
||||
*/
|
||||
setPreventRefreshOnChange: React.Dispatch<React.SetStateAction<boolean>>
|
||||
/**
|
||||
* Sets the selected tenant ID
|
||||
*
|
||||
* @param args.id - The ID of the tenant to select
|
||||
* @param args.refresh - Whether to refresh the page after changing the tenant
|
||||
*/
|
||||
setTenant: (args: { id: number | string | undefined; refresh?: boolean }) => void
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ const collectionAccessKeys: AllAccessKeys<
|
||||
type Args<ConfigType> = {
|
||||
collection: CollectionConfig
|
||||
fieldName: string
|
||||
tenantsArrayFieldName?: string
|
||||
tenantsArrayTenantFieldName?: string
|
||||
userHasAccessToAllTenants: Required<
|
||||
MultiTenantPluginConfig<ConfigType>
|
||||
>['userHasAccessToAllTenants']
|
||||
@@ -32,6 +34,8 @@ type Args<ConfigType> = {
|
||||
export const addCollectionAccess = <ConfigType>({
|
||||
collection,
|
||||
fieldName,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
}: Args<ConfigType>): void => {
|
||||
collectionAccessKeys.forEach((key) => {
|
||||
@@ -40,7 +44,11 @@ export const addCollectionAccess = <ConfigType>({
|
||||
}
|
||||
collection.access[key] = withTenantAccess<ConfigType>({
|
||||
accessFunction: collection.access?.[key],
|
||||
collection,
|
||||
fieldName: key === 'readVersions' ? `version.${fieldName}` : fieldName,
|
||||
operation: key,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,6 +52,7 @@ export async function getGlobalViewRedirect({
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
overrideAccess: false,
|
||||
pagination: false,
|
||||
user,
|
||||
where: {
|
||||
[tenantFieldName]: {
|
||||
|
||||
@@ -2,14 +2,25 @@ import type { Where } from 'payload'
|
||||
|
||||
import type { UserWithTenantsField } from '../types.js'
|
||||
|
||||
import { defaults } from '../defaults.js'
|
||||
import { getUserTenantIDs } from './getUserTenantIDs.js'
|
||||
|
||||
type Args = {
|
||||
fieldName: string
|
||||
tenantsArrayFieldName?: string
|
||||
tenantsArrayTenantFieldName?: string
|
||||
user: UserWithTenantsField
|
||||
}
|
||||
export function getTenantAccess({ fieldName, user }: Args): Where {
|
||||
const userAssignedTenantIDs = getUserTenantIDs(user)
|
||||
export function getTenantAccess({
|
||||
fieldName,
|
||||
tenantsArrayFieldName = defaults.tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName,
|
||||
user,
|
||||
}: Args): Where {
|
||||
const userAssignedTenantIDs = getUserTenantIDs(user, {
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
})
|
||||
|
||||
return {
|
||||
[fieldName]: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Tenant, UserWithTenantsField } from '../types.js'
|
||||
|
||||
import { defaults } from '../defaults.js'
|
||||
import { extractID } from './extractID.js'
|
||||
|
||||
/**
|
||||
@@ -9,15 +10,26 @@ import { extractID } from './extractID.js'
|
||||
*/
|
||||
export const getUserTenantIDs = <IDType extends number | string>(
|
||||
user: null | UserWithTenantsField,
|
||||
options?: {
|
||||
tenantsArrayFieldName?: string
|
||||
tenantsArrayTenantFieldName?: string
|
||||
},
|
||||
): IDType[] => {
|
||||
if (!user) {
|
||||
return []
|
||||
}
|
||||
|
||||
const {
|
||||
tenantsArrayFieldName = defaults.tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName,
|
||||
} = options || {}
|
||||
|
||||
return (
|
||||
user?.tenants?.reduce<IDType[]>((acc, { tenant }) => {
|
||||
if (tenant) {
|
||||
acc.push(extractID<IDType>(tenant as Tenant<IDType>))
|
||||
(Array.isArray(user[tenantsArrayFieldName]) ? user[tenantsArrayFieldName] : [])?.reduce<
|
||||
IDType[]
|
||||
>((acc, row) => {
|
||||
if (row[tenantsArrayTenantFieldName]) {
|
||||
acc.push(extractID<IDType>(row[tenantsArrayTenantFieldName] as Tenant<IDType>))
|
||||
}
|
||||
|
||||
return acc
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import type { Access, AccessArgs, AccessResult, User } from 'payload'
|
||||
import type {
|
||||
Access,
|
||||
AccessArgs,
|
||||
AccessResult,
|
||||
AllOperations,
|
||||
CollectionConfig,
|
||||
User,
|
||||
Where,
|
||||
} from 'payload'
|
||||
|
||||
import type { MultiTenantPluginConfig, UserWithTenantsField } from '../types.js'
|
||||
|
||||
@@ -7,15 +15,26 @@ import { getTenantAccess } from './getTenantAccess.js'
|
||||
|
||||
type Args<ConfigType> = {
|
||||
accessFunction?: Access
|
||||
collection: CollectionConfig
|
||||
fieldName: string
|
||||
operation: AllOperations
|
||||
tenantsArrayFieldName?: string
|
||||
tenantsArrayTenantFieldName?: string
|
||||
userHasAccessToAllTenants: Required<
|
||||
MultiTenantPluginConfig<ConfigType>
|
||||
>['userHasAccessToAllTenants']
|
||||
}
|
||||
export const withTenantAccess =
|
||||
<ConfigType>({ accessFunction, fieldName, userHasAccessToAllTenants }: Args<ConfigType>) =>
|
||||
<ConfigType>({
|
||||
accessFunction,
|
||||
collection,
|
||||
fieldName,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
}: Args<ConfigType>) =>
|
||||
async (args: AccessArgs): Promise<AccessResult> => {
|
||||
const constraints = []
|
||||
const constraints: Where[] = []
|
||||
const accessFn =
|
||||
typeof accessFunction === 'function'
|
||||
? accessFunction
|
||||
@@ -34,12 +53,26 @@ export const withTenantAccess =
|
||||
args.req.user as ConfigType extends { user: unknown } ? ConfigType['user'] : User,
|
||||
)
|
||||
) {
|
||||
constraints.push(
|
||||
getTenantAccess({
|
||||
fieldName,
|
||||
user: args.req.user as UserWithTenantsField,
|
||||
}),
|
||||
)
|
||||
const tenantConstraint = getTenantAccess({
|
||||
fieldName,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
user: args.req.user as UserWithTenantsField,
|
||||
})
|
||||
if (collection.slug === args.req.user.collection) {
|
||||
constraints.push({
|
||||
or: [
|
||||
{
|
||||
id: {
|
||||
equals: args.req.user.id,
|
||||
},
|
||||
},
|
||||
tenantConstraint,
|
||||
],
|
||||
})
|
||||
} else {
|
||||
constraints.push(tenantConstraint)
|
||||
}
|
||||
return combineWhereConstraints(constraints)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user