Compare commits
12 Commits
db-postgre
...
fix/recurs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dd28e41d8 | ||
|
|
9bbacc4fb1 | ||
|
|
7df7bf448b | ||
|
|
44599cbc7b | ||
|
|
373787de31 | ||
|
|
c1c86009a5 | ||
|
|
6cf6ca3ea8 | ||
|
|
6706bdb140 | ||
|
|
5caaa032bb | ||
|
|
4cf8c5bd78 | ||
|
|
742a7af93d | ||
|
|
a7e7c92768 |
@@ -1,3 +1,11 @@
|
|||||||
|
## [2.12.1](https://github.com/payloadcms/payload/compare/v2.12.0...v2.12.1) (2024-04-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Skip parsing if operator is 'exist' ([#5404](https://github.com/payloadcms/payload/issues/5404)) ([742a7af](https://github.com/payloadcms/payload/commit/742a7af93d2e9ef4d41ee093cef875322792ae72))
|
||||||
|
* updates colors of tooltip in light mode ([#5632](https://github.com/payloadcms/payload/issues/5632)) ([a7e7c92](https://github.com/payloadcms/payload/commit/a7e7c9276835e0a35a18daccb218c901ca2fdd8c))
|
||||||
|
|
||||||
## [2.12.0](https://github.com/payloadcms/payload/compare/v2.11.2...v2.12.0) (2024-04-03)
|
## [2.12.0](https://github.com/payloadcms/payload/compare/v2.11.2...v2.12.0) (2024-04-03)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ import * as React from 'react'
|
|||||||
import {
|
import {
|
||||||
CustomSaveButtonProps,
|
CustomSaveButtonProps,
|
||||||
CustomSaveDraftButtonProps,
|
CustomSaveDraftButtonProps,
|
||||||
CustomPublishButtonProps,
|
CustomPublishButtonType,
|
||||||
CustomPreviewButtonProps,
|
CustomPreviewButtonProps,
|
||||||
} from 'payload/types'
|
} from 'payload/types'
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ export const CustomSaveDraftButton: CustomSaveDraftButtonProps = ({
|
|||||||
return <DefaultButton label={label} disabled={disabled} saveDraft={saveDraft} />
|
return <DefaultButton label={label} disabled={disabled} saveDraft={saveDraft} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomPublishButton: CustomPublishButtonProps = ({
|
export const CustomPublishButton: CustomPublishButtonType = ({
|
||||||
DefaultButton,
|
DefaultButton,
|
||||||
disabled,
|
disabled,
|
||||||
label,
|
label,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ It's often best practice to write your Collections in separate files and then im
|
|||||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||||
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
|
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
|
||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
|
| **`dbName`** | Custom table or collection name depending on the database adapter. Auto-generated from slug if not defined.
|
||||||
|
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
@@ -59,7 +60,8 @@ export const Orders: CollectionConfig = {
|
|||||||
#### More collection config examples
|
#### More collection config examples
|
||||||
|
|
||||||
You can find an assortment
|
You can find an assortment
|
||||||
of [example collection configs](https://github.com/payloadcms/public-demo/tree/master/src/payload/collections) in the Public
|
of [example collection configs](https://github.com/payloadcms/public-demo/tree/master/src/payload/collections) in the
|
||||||
|
Public
|
||||||
Demo source code on GitHub.
|
Demo source code on GitHub.
|
||||||
|
|
||||||
### Admin options
|
### Admin options
|
||||||
|
|||||||
@@ -6,14 +6,18 @@ desc: Set up your Global config for your needs by defining fields, adding slugs
|
|||||||
keywords: globals, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
keywords: globals, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||||
---
|
---
|
||||||
|
|
||||||
Global configs are in many ways similar to [Collections](/docs/configuration/collections). The big difference is that Collections will potentially contain _many_ documents, while a Global is a "one-off". Globals are perfect for things like header nav, site-wide banner alerts, app-wide localized strings, and other "global" data that your site or app might rely on.
|
Global configs are in many ways similar to [Collections](/docs/configuration/collections). The big difference is that
|
||||||
|
Collections will potentially contain _many_ documents, while a Global is a "one-off". Globals are perfect for things
|
||||||
|
like header nav, site-wide banner alerts, app-wide localized strings, and other "global" data that your site or app
|
||||||
|
might rely on.
|
||||||
|
|
||||||
As with Collection configs, it's often best practice to write your Globals in separate files and then import them into the main Payload config.
|
As with Collection configs, it's often best practice to write your Globals in separate files and then import them into
|
||||||
|
the main Payload config.
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Global. |
|
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Global. |
|
||||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
|
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
|
||||||
| **`label`** | Text for the name in the Admin panel or an object with keys for each language. Auto-generated from slug if not defined. |
|
| **`label`** | Text for the name in the Admin panel or an object with keys for each language. Auto-generated from slug if not defined. |
|
||||||
@@ -26,6 +30,7 @@ As with Collection configs, it's often best practice to write your Globals in se
|
|||||||
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
|
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
|
||||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
|
| **`dbName`** | Custom table or collection name for this global depending on the database adapter. Auto-generated from slug if not defined.
|
||||||
|
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
@@ -59,26 +64,30 @@ export default Nav
|
|||||||
|
|
||||||
#### Global config example
|
#### Global config example
|
||||||
|
|
||||||
You can find a few [example Global configs](https://github.com/payloadcms/public-demo/tree/master/src/payload/globals) in the Public Demo source code on GitHub.
|
You can find a few [example Global configs](https://github.com/payloadcms/public-demo/tree/master/src/payload/globals)
|
||||||
|
in the Public Demo source code on GitHub.
|
||||||
|
|
||||||
### Admin options
|
### Admin options
|
||||||
|
|
||||||
You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a Global's config.
|
You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a
|
||||||
|
Global's config.
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|---------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `group` | Text used as a label for grouping collection and global links together in the navigation. |
|
| `group` | Text used as a label for grouping collection and global links together in the navigation. |
|
||||||
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. |
|
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. |
|
||||||
| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
|
| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
|
||||||
| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
|
| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
|
||||||
| `livePreview`| Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
||||||
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
|
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
|
||||||
|
|
||||||
### Preview
|
### Preview
|
||||||
|
|
||||||
Global `admin` options can accept a `preview` function that will be used to generate a link pointing to the frontend of your app to preview data.
|
Global `admin` options can accept a `preview` function that will be used to generate a link pointing to the frontend of
|
||||||
|
your app to preview data.
|
||||||
|
|
||||||
If the function is specified, a Preview button will automatically appear in the corresponding global's Edit view. Clicking the Preview button will link to the URL that is generated by the function.
|
If the function is specified, a Preview button will automatically appear in the corresponding global's Edit view.
|
||||||
|
Clicking the Preview button will link to the URL that is generated by the function.
|
||||||
|
|
||||||
**The preview function accepts two arguments:**
|
**The preview function accepts two arguments:**
|
||||||
|
|
||||||
@@ -113,15 +122,20 @@ export const MyGlobal: GlobalConfig = {
|
|||||||
|
|
||||||
### Access control
|
### Access control
|
||||||
|
|
||||||
As with Collections, you can specify extremely granular access control (what users can do with this Global) on a Global-by-Global basis. However, Globals only have `update` and `read` access control due to their nature of only having one document. To learn more, go to the [Access Control](/docs/access-control/overview) docs.
|
As with Collections, you can specify extremely granular access control (what users can do with this Global) on a
|
||||||
|
Global-by-Global basis. However, Globals only have `update` and `read` access control due to their nature of only having
|
||||||
|
one document. To learn more, go to the [Access Control](/docs/access-control/overview) docs.
|
||||||
|
|
||||||
### Hooks
|
### Hooks
|
||||||
|
|
||||||
Globals also fully support a smaller subset of Hooks. To learn more, go to the [Hooks](/docs/hooks/overview) documentation.
|
Globals also fully support a smaller subset of Hooks. To learn more, go to the [Hooks](/docs/hooks/overview)
|
||||||
|
documentation.
|
||||||
|
|
||||||
### Field types
|
### Field types
|
||||||
|
|
||||||
Globals support all field types that Payload has to offer—including simple fields like text and checkboxes all the way to more complicated layout-building field groups like Blocks. [Click here](/docs/fields/overview) to learn more about field types.
|
Globals support all field types that Payload has to offer—including simple fields like text and checkboxes all the way
|
||||||
|
to more complicated layout-building field groups like Blocks. [Click here](/docs/fields/overview) to learn more about
|
||||||
|
field types.
|
||||||
|
|
||||||
### TypeScript
|
### TypeScript
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,18 @@ export default buildConfig({
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `pool` | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
|
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
|
||||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||||
|
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||||
| `schemaName` | A string for the postgres schema to use, defaults to 'public'. |
|
| `schemaName` | A string for the postgres schema to use, defaults to 'public'. |
|
||||||
|
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
|
||||||
|
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
|
||||||
|
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Access to Drizzle
|
### Access to Drizzle
|
||||||
|
|
||||||
|
|||||||
@@ -12,22 +12,24 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
|||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
<LightDarkImage
|
<LightDarkImage
|
||||||
srcLight="https://payloadcms.com/images/docs/fields/array.png"
|
srcLight="https://payloadcms.com/images/docs/fields/array.png"
|
||||||
srcDark="https://payloadcms.com/images/docs/fields/array-dark.png"
|
srcDark="https://payloadcms.com/images/docs/fields/array-dark.png"
|
||||||
alt="Array field with two Rows in Payload admin panel"
|
alt="Array field with two Rows in Payload admin panel"
|
||||||
caption="Admin panel screenshot of an Array field with two Rows"
|
caption="Admin panel screenshot of an Array field with two Rows"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
**Example uses:**
|
**Example uses:**
|
||||||
|
|
||||||
- A "slider" with an image ([upload field](/docs/fields/upload)) and a caption ([text field](/docs/fields/text))
|
- A "slider" with an image ([upload field](/docs/fields/upload)) and a caption ([text field](/docs/fields/text))
|
||||||
- Navigational structures where editors can specify nav items containing pages ([relationship field](/docs/fields/relationship)), an "open in new tab" [checkbox field](/docs/fields/checkbox)
|
- Navigational structures where editors can specify nav items containing
|
||||||
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/fields/date)), label ([text field](/docs/fields/text)), and Learn More page [relationship](/docs/fields/relationship)
|
pages ([relationship field](/docs/fields/relationship)), an "open in new tab" [checkbox field](/docs/fields/checkbox)
|
||||||
|
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/fields/date)),
|
||||||
|
label ([text field](/docs/fields/text)), and Learn More page [relationship](/docs/fields/relationship)
|
||||||
|
|
||||||
### Config
|
### Config
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||||
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
|
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
|
||||||
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
|
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
|
||||||
@@ -45,15 +47,17 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
|||||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||||
|
| **`dbName`** | Custom table name for the field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||||
|
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
### Admin Config
|
### Admin Config
|
||||||
|
|
||||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following
|
||||||
|
properties:
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|---------------------------|----------------------------------------------------------------------------------------------------------------------|
|
||||||
| **`initCollapsed`** | Set the initial collapsed state |
|
| **`initCollapsed`** | Set the initial collapsed state |
|
||||||
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
|
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
|
||||||
|
|
||||||
|
|||||||
@@ -14,22 +14,23 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
|
|||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
<LightDarkImage
|
<LightDarkImage
|
||||||
srcLight="https://payloadcms.com/images/docs/fields/blocks.png"
|
srcLight="https://payloadcms.com/images/docs/fields/blocks.png"
|
||||||
srcDark="https://payloadcms.com/images/docs/fields/blocks-dark.png"
|
srcDark="https://payloadcms.com/images/docs/fields/blocks-dark.png"
|
||||||
alt="Admin panel screenshot of add Blocks drawer view"
|
alt="Admin panel screenshot of add Blocks drawer view"
|
||||||
caption="Admin panel screenshot of add Blocks drawer view"
|
caption="Admin panel screenshot of add Blocks drawer view"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
**Example uses:**
|
**Example uses:**
|
||||||
|
|
||||||
- A layout builder tool that grants editors to design highly customizable page or post layouts. Blocks could include configs such as `Quote`, `CallToAction`, `Slider`, `Content`, `Gallery`, or others.
|
- A layout builder tool that grants editors to design highly customizable page or post layouts. Blocks could include
|
||||||
|
configs such as `Quote`, `CallToAction`, `Slider`, `Content`, `Gallery`, or others.
|
||||||
- A form builder tool where available block configs might be `Text`, `Select`, or `Checkbox`.
|
- A form builder tool where available block configs might be `Text`, `Select`, or `Checkbox`.
|
||||||
- Virtual event agenda "timeslots" where a timeslot could either be a `Break`, a `Presentation`, or a `BreakoutSession`.
|
- Virtual event agenda "timeslots" where a timeslot could either be a `Break`, a `Presentation`, or a `BreakoutSession`.
|
||||||
|
|
||||||
### Field config
|
### Field config
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||||
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
|
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
|
||||||
| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
|
| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
|
||||||
@@ -51,10 +52,11 @@ _\* An asterisk denotes that a property is required._
|
|||||||
|
|
||||||
### Admin Config
|
### Admin Config
|
||||||
|
|
||||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following
|
||||||
|
properties:
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ------------------- | ------------------------------- |
|
|---------------------|---------------------------------|
|
||||||
| **`initCollapsed`** | Set the initial collapsed state |
|
| **`initCollapsed`** | Set the initial collapsed state |
|
||||||
|
|
||||||
### Block configs
|
### Block configs
|
||||||
@@ -72,7 +74,7 @@ Blocks are defined as separate configs of their own.
|
|||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| **`slug`** \* | Identifier for this block type. Will be saved on each block as the `blockType` property. |
|
| **`slug`** \* | Identifier for this block type. Will be saved on each block as the `blockType` property. |
|
||||||
| **`fields`** \* | Array of fields to be stored in this block. |
|
| **`fields`** \* | Array of fields to be stored in this block. |
|
||||||
| **`labels`** | Customize the block labels that appear in the Admin dashboard. Auto-generated from slug if not defined. |
|
| **`labels`** | Customize the block labels that appear in the Admin dashboard. Auto-generated from slug if not defined. |
|
||||||
@@ -80,7 +82,8 @@ Blocks are defined as separate configs of their own.
|
|||||||
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
|
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
|
||||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||||
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. |
|
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. |
|
||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`dbName`** | Custom table name for this block type when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from slug if not defined.
|
||||||
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
|
|
||||||
#### Auto-generated data per block
|
#### Auto-generated data per block
|
||||||
|
|
||||||
@@ -92,7 +95,8 @@ The `blockType` is saved as the slug of the block that has been selected.
|
|||||||
|
|
||||||
**`blockName`**
|
**`blockName`**
|
||||||
|
|
||||||
The Admin panel provides each block with a `blockName` field which optionally allows editors to label their blocks for better editability and readability.
|
The Admin panel provides each block with a `blockName` field which optionally allows editors to label their blocks for
|
||||||
|
better editability and readability.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
@@ -139,7 +143,8 @@ export const ExampleCollection: CollectionConfig = {
|
|||||||
|
|
||||||
### TypeScript
|
### TypeScript
|
||||||
|
|
||||||
As you build your own Block configs, you might want to store them in separate files but retain typing accordingly. To do so, you can import and use Payload's `Block` type:
|
As you build your own Block configs, you might want to store them in separate files but retain typing accordingly. To do
|
||||||
|
so, you can import and use Payload's `Block` type:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import type { Block } from 'payload/types'
|
import type { Block } from 'payload/types'
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
|
|||||||
| **`required`** | Require this field to have a value. |
|
| **`required`** | Require this field to have a value. |
|
||||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
|
| **`enumName`** | Custom enum name for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined.
|
||||||
|
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
|
|||||||
@@ -12,32 +12,34 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
|
|||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
<LightDarkImage
|
<LightDarkImage
|
||||||
srcLight="https://payloadcms.com/images/docs/fields/select.png"
|
srcLight="https://payloadcms.com/images/docs/fields/select.png"
|
||||||
srcDark="https://payloadcms.com/images/docs/fields/select-dark.png"
|
srcDark="https://payloadcms.com/images/docs/fields/select-dark.png"
|
||||||
alt="Shows a Select field in the Payload admin panel"
|
alt="Shows a Select field in the Payload admin panel"
|
||||||
caption="Admin panel screenshot of a Select field"
|
caption="Admin panel screenshot of a Select field"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
### Config
|
### Config
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||||
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
|
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
|
||||||
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. |
|
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. |
|
||||||
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
|
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
|
||||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
||||||
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
||||||
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
|
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
|
||||||
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
|
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
|
||||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||||
| **`required`** | Require this field to have a value. |
|
| **`required`** | Require this field to have a value. |
|
||||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
|
| **`enumName`** | Custom enum name for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||||
|
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||||
|
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
@@ -52,7 +54,8 @@ _\* An asterisk denotes that a property is required._
|
|||||||
|
|
||||||
### Admin config
|
### Admin config
|
||||||
|
|
||||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Select field type also allows for the following admin-specific properties:
|
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Select field type also allows
|
||||||
|
for the following admin-specific properties:
|
||||||
|
|
||||||
**`isClearable`**
|
**`isClearable`**
|
||||||
|
|
||||||
@@ -60,7 +63,8 @@ Set to `true` if you'd like this field to be clearable within the Admin UI.
|
|||||||
|
|
||||||
**`isSortable`**
|
**`isSortable`**
|
||||||
|
|
||||||
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`)
|
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works
|
||||||
|
when `hasMany` is set to `true`)
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
@@ -101,7 +105,8 @@ export const ExampleCollection: CollectionConfig = {
|
|||||||
|
|
||||||
### Customization
|
### Customization
|
||||||
|
|
||||||
The Select field UI component can be customized by providing a custom React component to the `components` object in the Base config.
|
The Select field UI component can be customized by providing a custom React component to the `components` object in the
|
||||||
|
Base config.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const CustomSelectField: Field = {
|
export const CustomSelectField: Field = {
|
||||||
@@ -156,27 +161,33 @@ export const CustomSelectComponent: React.FC<CustomSelectProps> = ({ path, optio
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className="field-label">
|
<label className = "field-label" >
|
||||||
Custom Select
|
Custom
|
||||||
</label>
|
Select
|
||||||
<SelectInput
|
< /label>
|
||||||
path={path}
|
< SelectInput
|
||||||
name={path}
|
path = { path }
|
||||||
options={adjustedOptions}
|
name = { path }
|
||||||
value={value}
|
options = { adjustedOptions }
|
||||||
onChange={(e) => setValue(e.value)}
|
value = { value }
|
||||||
/>
|
onChange = {(e)
|
||||||
</div>
|
=>
|
||||||
)
|
setValue(e.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
< /div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are looking to create a dynamic select field, the following tutorial will walk you through the process of creating a custom select field that fetches its options from an external API.
|
If you are looking to create a dynamic select field, the following tutorial will walk you through the process of
|
||||||
|
creating a custom select field that fetches its options from an external API.
|
||||||
|
|
||||||
<VideoDrawer
|
<VideoDrawer
|
||||||
id='Efn9OxSjA6Y'
|
id='Efn9OxSjA6Y'
|
||||||
label='How to Create a Custom Select Field'
|
label='How to Create a Custom Select Field'
|
||||||
drawerTitle='How to Create a Custom Select Field: A Step-by-Step Guide'
|
drawerTitle='How to Create a Custom Select Field: A Step-by-Step Guide'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
If you want to learn more about custom components check out the [Admin > Custom Component](/docs/admin/components#field-component) docs.
|
If you want to learn more about custom components check out
|
||||||
|
the [Admin > Custom Component](/docs/admin/components#field-component) docs.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/db-mongodb",
|
"name": "@payloadcms/db-mongodb",
|
||||||
"version": "1.4.3",
|
"version": "1.4.4",
|
||||||
"description": "The officially supported MongoDB database adapter for Payload",
|
"description": "The officially supported MongoDB database adapter for Payload",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { withSession } from './withSession'
|
|||||||
|
|
||||||
export const createGlobal: CreateGlobal = async function createGlobal(
|
export const createGlobal: CreateGlobal = async function createGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ data, req = {} as PayloadRequest, slug },
|
{ slug, data, req = {} as PayloadRequest },
|
||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
const global = {
|
const global = {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { withSession } from './withSession'
|
|||||||
|
|
||||||
export const findGlobal: FindGlobal = async function findGlobal(
|
export const findGlobal: FindGlobal = async function findGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ locale, req = {} as PayloadRequest, slug, where },
|
{ slug, locale, req = {} as PayloadRequest, where },
|
||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
const options = {
|
const options = {
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ import buildCollectionSchema from './models/buildCollectionSchema'
|
|||||||
import { buildGlobalModel } from './models/buildGlobalModel'
|
import { buildGlobalModel } from './models/buildGlobalModel'
|
||||||
import buildSchema from './models/buildSchema'
|
import buildSchema from './models/buildSchema'
|
||||||
import getBuildQueryPlugin from './queries/buildQuery'
|
import getBuildQueryPlugin from './queries/buildQuery'
|
||||||
|
import { getDBName } from './utilities/getDBName'
|
||||||
|
|
||||||
export const init: Init = async function init(this: MongooseAdapter) {
|
export const init: Init = async function init(this: MongooseAdapter) {
|
||||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||||
const schema = buildCollectionSchema(collection, this.payload.config)
|
const schema = buildCollectionSchema(collection, this.payload.config)
|
||||||
|
|
||||||
if (collection.versions) {
|
if (collection.versions) {
|
||||||
const versionModelName = getVersionsModelName(collection)
|
const versionModelName = getDBName({ config: collection, versions: true })
|
||||||
|
|
||||||
const versionCollectionFields = buildVersionCollectionFields(collection)
|
const versionCollectionFields = buildVersionCollectionFields(collection)
|
||||||
|
|
||||||
@@ -54,12 +55,11 @@ export const init: Init = async function init(this: MongooseAdapter) {
|
|||||||
versionSchema,
|
versionSchema,
|
||||||
this.autoPluralization === true ? undefined : versionModelName,
|
this.autoPluralization === true ? undefined : versionModelName,
|
||||||
) as CollectionModel
|
) as CollectionModel
|
||||||
// this.payload.versions[collection.slug] = model;
|
|
||||||
this.versions[collection.slug] = model
|
this.versions[collection.slug] = model
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = mongoose.model(
|
const model = mongoose.model(
|
||||||
collection.slug,
|
getDBName({ config: collection }),
|
||||||
schema,
|
schema,
|
||||||
this.autoPluralization === true ? undefined : collection.slug,
|
this.autoPluralization === true ? undefined : collection.slug,
|
||||||
) as CollectionModel
|
) as CollectionModel
|
||||||
@@ -77,7 +77,7 @@ export const init: Init = async function init(this: MongooseAdapter) {
|
|||||||
|
|
||||||
this.payload.config.globals.forEach((global) => {
|
this.payload.config.globals.forEach((global) => {
|
||||||
if (global.versions) {
|
if (global.versions) {
|
||||||
const versionModelName = getVersionsModelName(global)
|
const versionModelName = getDBName({ config: global, versions: true })
|
||||||
|
|
||||||
const versionGlobalFields = buildVersionGlobalFields(global)
|
const versionGlobalFields = buildVersionGlobalFields(global)
|
||||||
|
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
|||||||
}
|
}
|
||||||
if (field.localized && config.localization) {
|
if (field.localized && config.localization) {
|
||||||
config.localization.locales.forEach((locale) => {
|
config.localization.locales.forEach((locale) => {
|
||||||
schema.index({ [`${field.name}.${locale}`]: '2dsphere' }, indexOptions)
|
schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
schema.index({ [field.name]: '2dsphere' }, indexOptions)
|
schema.index({ [field.name]: '2dsphere' }, indexOptions)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export const sanitizeQueryValue = ({
|
|||||||
formattedValue = Number(val)
|
formattedValue = Number(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'date' && typeof val === 'string') {
|
if (field.type === 'date' && typeof val === 'string' && operator !== 'exists') {
|
||||||
formattedValue = new Date(val)
|
formattedValue = new Date(val)
|
||||||
if (Number.isNaN(Date.parse(formattedValue))) {
|
if (Number.isNaN(Date.parse(formattedValue))) {
|
||||||
return undefined
|
return undefined
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { withSession } from './withSession'
|
|||||||
|
|
||||||
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ data, req = {} as PayloadRequest, slug },
|
{ slug, data, req = {} as PayloadRequest },
|
||||||
) {
|
) {
|
||||||
const Model = this.globals
|
const Model = this.globals
|
||||||
const options = {
|
const options = {
|
||||||
|
|||||||
41
packages/db-mongodb/src/utilities/getDBName.ts
Normal file
41
packages/db-mongodb/src/utilities/getDBName.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { DBIdentifierName } from 'payload/database'
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
config: {
|
||||||
|
dbName?: DBIdentifierName
|
||||||
|
enumName?: DBIdentifierName
|
||||||
|
name?: string
|
||||||
|
slug?: string
|
||||||
|
}
|
||||||
|
locales?: boolean
|
||||||
|
target?: 'dbName' | 'enumName'
|
||||||
|
versions?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to name database enums and collections
|
||||||
|
* Returns the collection or enum name for a given entity
|
||||||
|
*/
|
||||||
|
export const getDBName = ({
|
||||||
|
config: { name, slug },
|
||||||
|
config,
|
||||||
|
target = 'dbName',
|
||||||
|
versions = false,
|
||||||
|
}: Args): string => {
|
||||||
|
let result: string
|
||||||
|
let custom = config[target]
|
||||||
|
|
||||||
|
if (!custom && target === 'enumName') {
|
||||||
|
custom = config['dbName']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom) {
|
||||||
|
result = typeof custom === 'function' ? custom({}) : custom
|
||||||
|
} else {
|
||||||
|
result = name ?? slug
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versions) result = `_${result}_versions`
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import type { Create } from 'payload/database'
|
import type { Create } from 'payload/database'
|
||||||
|
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export const create: Create = async function create(
|
export const create: Create = async function create(
|
||||||
@@ -19,8 +18,11 @@ export const create: Create = async function create(
|
|||||||
db,
|
db,
|
||||||
fields: collection.fields,
|
fields: collection.fields,
|
||||||
operation: 'create',
|
operation: 'create',
|
||||||
tableName: toSnakeCase(collectionSlug),
|
|
||||||
req,
|
req,
|
||||||
|
tableName: getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collection,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import type { CreateGlobalArgs } from 'payload/database'
|
import type { CreateGlobalArgs } from 'payload/database'
|
||||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function createGlobal<T extends TypeWithID>(
|
export async function createGlobal<T extends TypeWithID>(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
{ data, req = {} as PayloadRequest, slug }: CreateGlobalArgs,
|
{ slug, data, req = {} as PayloadRequest }: CreateGlobalArgs,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||||
@@ -20,8 +19,11 @@ export async function createGlobal<T extends TypeWithID>(
|
|||||||
db,
|
db,
|
||||||
fields: globalConfig.fields,
|
fields: globalConfig.fields,
|
||||||
operation: 'create',
|
operation: 'create',
|
||||||
tableName: toSnakeCase(slug),
|
|
||||||
req,
|
req,
|
||||||
|
tableName: getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: globalConfig,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { TypeWithVersion } from 'payload/database'
|
import type { TypeWithVersion } from 'payload/database'
|
||||||
import { type CreateGlobalVersionArgs } from 'payload/database'
|
|
||||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
import { sql } from 'drizzle-orm'
|
import { sql } from 'drizzle-orm'
|
||||||
|
import { type CreateGlobalVersionArgs } from 'payload/database'
|
||||||
import { buildVersionGlobalFields } from 'payload/versions'
|
import { buildVersionGlobalFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function createGlobalVersion<T extends TypeWithID>(
|
export async function createGlobalVersion<T extends TypeWithID>(
|
||||||
@@ -16,8 +16,11 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
|
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
|
||||||
const globalTableName = toSnakeCase(globalSlug)
|
const tableName = getTableName({
|
||||||
const tableName = `_${globalTableName}_v`
|
adapter: this,
|
||||||
|
config: global,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
|
|
||||||
const result = await upsertRow<TypeWithVersion<T>>({
|
const result = await upsertRow<TypeWithVersion<T>>({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
@@ -29,17 +32,17 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
|||||||
db,
|
db,
|
||||||
fields: buildVersionGlobalFields(global),
|
fields: buildVersionGlobalFields(global),
|
||||||
operation: 'create',
|
operation: 'create',
|
||||||
tableName,
|
|
||||||
req,
|
req,
|
||||||
|
tableName,
|
||||||
})
|
})
|
||||||
|
|
||||||
const table = this.tables[tableName]
|
const table = this.tables[tableName]
|
||||||
|
|
||||||
if (global.versions.drafts) {
|
if (global.versions.drafts) {
|
||||||
await db.execute(sql`
|
await db.execute(sql`
|
||||||
UPDATE ${table}
|
UPDATE ${table}
|
||||||
SET latest = false
|
SET latest = false
|
||||||
WHERE ${table.id} != ${result.id};
|
WHERE ${table.id} != ${result.id};
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
|
|||||||
|
|
||||||
import { sql } from 'drizzle-orm'
|
import { sql } from 'drizzle-orm'
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function createVersion<T extends TypeWithID>(
|
export async function createVersion<T extends TypeWithID>(
|
||||||
@@ -21,8 +21,11 @@ export async function createVersion<T extends TypeWithID>(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collection = this.payload.collections[collectionSlug].config
|
const collection = this.payload.collections[collectionSlug].config
|
||||||
const collectionTableName = toSnakeCase(collectionSlug)
|
const tableName = getTableName({
|
||||||
const tableName = `_${collectionTableName}_v`
|
adapter: this,
|
||||||
|
config: collection,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
|
|
||||||
const result = await upsertRow<TypeWithVersion<T>>({
|
const result = await upsertRow<TypeWithVersion<T>>({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
@@ -35,22 +38,30 @@ export async function createVersion<T extends TypeWithID>(
|
|||||||
db,
|
db,
|
||||||
fields: buildVersionCollectionFields(collection),
|
fields: buildVersionCollectionFields(collection),
|
||||||
operation: 'create',
|
operation: 'create',
|
||||||
tableName,
|
|
||||||
req,
|
req,
|
||||||
|
tableName,
|
||||||
})
|
})
|
||||||
|
|
||||||
const table = this.tables[tableName]
|
const table = this.tables[tableName]
|
||||||
const relationshipsTable = this.tables[`${tableName}_rels`]
|
const relationshipsTable =
|
||||||
|
this.tables[
|
||||||
|
getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collection,
|
||||||
|
relationships: true,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
if (collection.versions.drafts) {
|
if (collection.versions.drafts) {
|
||||||
await db.execute(sql`
|
await db.execute(sql`
|
||||||
UPDATE ${table}
|
UPDATE ${table}
|
||||||
SET latest = false
|
SET latest = false
|
||||||
FROM ${relationshipsTable}
|
FROM ${relationshipsTable}
|
||||||
WHERE ${table.id} = ${relationshipsTable.parent}
|
WHERE ${table.id} = ${relationshipsTable.parent}
|
||||||
AND ${relationshipsTable.path} = ${'parent'}
|
AND ${relationshipsTable.path} = ${'parent'}
|
||||||
AND ${relationshipsTable[`${collectionSlug}ID`]} = ${parent}
|
AND ${relationshipsTable[`${collectionSlug}ID`]} = ${parent}
|
||||||
AND ${table.id} != ${result.id};
|
AND ${table.id} != ${result.id};
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { DeleteMany } from 'payload/database'
|
|||||||
import type { PayloadRequest } from 'payload/types'
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
import { inArray } from 'drizzle-orm'
|
import { inArray } from 'drizzle-orm'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export const deleteMany: DeleteMany = async function deleteMany(
|
export const deleteMany: DeleteMany = async function deleteMany(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -14,7 +14,7 @@ export const deleteMany: DeleteMany = async function deleteMany(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collectionConfig = this.payload.collections[collection].config
|
const collectionConfig = this.payload.collections[collection].config
|
||||||
const tableName = toSnakeCase(collection)
|
const tableName = getTableName({ adapter: this, config: collectionConfig })
|
||||||
|
|
||||||
const result = await findMany({
|
const result = await findMany({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import type { DeleteOne } from 'payload/database'
|
|||||||
import type { PayloadRequest } from 'payload/types'
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
import { eq } from 'drizzle-orm'
|
import { eq } from 'drizzle-orm'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { buildFindManyArgs } from './find/buildFindManyArgs'
|
import { buildFindManyArgs } from './find/buildFindManyArgs'
|
||||||
import buildQuery from './queries/buildQuery'
|
import buildQuery from './queries/buildQuery'
|
||||||
import { selectDistinct } from './queries/selectDistinct'
|
import { selectDistinct } from './queries/selectDistinct'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { transform } from './transform/read'
|
import { transform } from './transform/read'
|
||||||
|
|
||||||
export const deleteOne: DeleteOne = async function deleteOne(
|
export const deleteOne: DeleteOne = async function deleteOne(
|
||||||
@@ -17,7 +17,10 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collection = this.payload.collections[collectionSlug].config
|
const collection = this.payload.collections[collectionSlug].config
|
||||||
const tableName = toSnakeCase(collectionSlug)
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collection,
|
||||||
|
})
|
||||||
let docToDelete: Record<string, unknown>
|
let docToDelete: Record<string, unknown>
|
||||||
|
|
||||||
const { joinAliases, joins, selectFields, where } = await buildQuery({
|
const { joinAliases, joins, selectFields, where } = await buildQuery({
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
|||||||
|
|
||||||
import { inArray } from 'drizzle-orm'
|
import { inArray } from 'drizzle-orm'
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export const deleteVersions: DeleteVersions = async function deleteVersion(
|
export const deleteVersions: DeleteVersions = async function deleteVersion(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -16,7 +16,11 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
|
|||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
|
|
||||||
const tableName = `_${toSnakeCase(collection)}_v`
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collectionConfig,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
const fields = buildVersionCollectionFields(collectionConfig)
|
const fields = buildVersionCollectionFields(collectionConfig)
|
||||||
|
|
||||||
const { docs } = await findMany({
|
const { docs } = await findMany({
|
||||||
|
|||||||
@@ -1,38 +1,41 @@
|
|||||||
import type { Find } from 'payload/database'
|
import type { Find } from 'payload/database'
|
||||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export const find: Find = async function find(
|
export const find: Find = async function find(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
{
|
{
|
||||||
collection,
|
collection,
|
||||||
limit: limitArg,
|
limit,
|
||||||
locale,
|
locale,
|
||||||
page = 1,
|
page = 1,
|
||||||
pagination,
|
pagination,
|
||||||
req = {} as PayloadRequest,
|
req = {} as PayloadRequest,
|
||||||
sort: sortArg,
|
sort: sortArg,
|
||||||
where: whereArg,
|
where,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||||
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collectionConfig,
|
||||||
|
})
|
||||||
|
|
||||||
return findMany({
|
return findMany({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
fields: collectionConfig.fields,
|
fields: collectionConfig.fields,
|
||||||
limit: limitArg,
|
limit,
|
||||||
locale,
|
locale,
|
||||||
page,
|
page,
|
||||||
pagination,
|
pagination,
|
||||||
req,
|
req,
|
||||||
sort,
|
sort,
|
||||||
tableName: toSnakeCase(collection),
|
tableName,
|
||||||
where: whereArg,
|
where,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const buildFindManyArgs = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adapter.tables[`${tableName}_rels`]) {
|
if (adapter.tables[`${tableName}${adapter.relationshipsSuffix}`]) {
|
||||||
result.with._rels = {
|
result.with._rels = {
|
||||||
columns: {
|
columns: {
|
||||||
id: false,
|
id: false,
|
||||||
@@ -63,7 +63,7 @@ export const buildFindManyArgs = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adapter.tables[`${tableName}_locales`]) {
|
if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) {
|
||||||
result.with._locales = _locales
|
result.with._locales = _locales
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
import type { Field } from 'payload/types'
|
import type { Field } from 'payload/types'
|
||||||
|
|
||||||
import { fieldAffectsData, tabHasName } from 'payload/types'
|
import { fieldAffectsData, tabHasName } from 'payload/types'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from '../types'
|
import type { PostgresAdapter } from '../types'
|
||||||
import type { Result } from './buildFindManyArgs'
|
import type { Result } from './buildFindManyArgs'
|
||||||
|
|
||||||
|
import { getTableName } from '../schema/getTableName'
|
||||||
|
|
||||||
type TraverseFieldArgs = {
|
type TraverseFieldArgs = {
|
||||||
_locales: Record<string, unknown>
|
_locales: Record<string, unknown>
|
||||||
adapter: PostgresAdapter
|
adapter: PostgresAdapter
|
||||||
@@ -78,9 +79,22 @@ export const traverseFields = ({
|
|||||||
with: {},
|
with: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const arrayTableName = `${currentTableName}_${path}${toSnakeCase(field.name)}`
|
const arrayTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: field,
|
||||||
|
parentTableName: currentTableName,
|
||||||
|
prefix: `${currentTableName}_${path}`,
|
||||||
|
})
|
||||||
|
|
||||||
if (adapter.tables[`${arrayTableName}_locales`]) withArray.with._locales = _locales
|
const arrayTableNameWithLocales = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: field,
|
||||||
|
locales: true,
|
||||||
|
parentTableName: currentTableName,
|
||||||
|
prefix: `${currentTableName}_${path}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (adapter.tables[arrayTableNameWithLocales]) withArray.with._locales = _locales
|
||||||
currentArgs.with[`${path}${field.name}`] = withArray
|
currentArgs.with[`${path}${field.name}`] = withArray
|
||||||
|
|
||||||
traverseFields({
|
traverseFields({
|
||||||
@@ -128,9 +142,15 @@ export const traverseFields = ({
|
|||||||
with: {},
|
with: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableName = `${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`
|
const tableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: block,
|
||||||
|
parentTableName: topLevelTableName,
|
||||||
|
prefix: `${topLevelTableName}_blocks_`,
|
||||||
|
})
|
||||||
|
|
||||||
if (adapter.tables[`${tableName}_locales`]) withBlock.with._locales = _locales
|
if (adapter.tables[`${tableName}${adapter.localesSuffix}`])
|
||||||
|
withBlock.with._locales = _locales
|
||||||
topLevelArgs.with[blockKey] = withBlock
|
topLevelArgs.with[blockKey] = withBlock
|
||||||
|
|
||||||
traverseFields({
|
traverseFields({
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import type { FindGlobal } from 'payload/database'
|
import type { FindGlobal } from 'payload/database'
|
||||||
|
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export const findGlobal: FindGlobal = async function findGlobal(
|
export const findGlobal: FindGlobal = async function findGlobal(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
{ locale, req, slug, where },
|
{ slug, locale, req, where },
|
||||||
) {
|
) {
|
||||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||||
const tableName = toSnakeCase(slug)
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: globalConfig,
|
||||||
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
docs: [doc],
|
docs: [doc],
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { FindGlobalVersions } from 'payload/database'
|
|||||||
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
|
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
|
||||||
|
|
||||||
import { buildVersionGlobalFields } from 'payload/versions'
|
import { buildVersionGlobalFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -27,7 +27,11 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
|||||||
)
|
)
|
||||||
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
|
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
|
||||||
|
|
||||||
const tableName = `_${toSnakeCase(global)}_v`
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: globalConfig,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
const fields = buildVersionGlobalFields(globalConfig)
|
const fields = buildVersionGlobalFields(globalConfig)
|
||||||
|
|
||||||
return findMany({
|
return findMany({
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import type { FindOneArgs } from 'payload/database'
|
import type { FindOneArgs } from 'payload/database'
|
||||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export async function findOne<T extends TypeWithID>(
|
export async function findOne<T extends TypeWithID>(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
{ collection, locale, req = {} as PayloadRequest, where: incomingWhere }: FindOneArgs,
|
{ collection, locale, req = {} as PayloadRequest, where }: FindOneArgs,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collectionConfig,
|
||||||
|
})
|
||||||
|
|
||||||
const { docs } = await findMany({
|
const { docs } = await findMany({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
@@ -22,8 +25,8 @@ export async function findOne<T extends TypeWithID>(
|
|||||||
pagination: false,
|
pagination: false,
|
||||||
req,
|
req,
|
||||||
sort: undefined,
|
sort: undefined,
|
||||||
tableName: toSnakeCase(collection),
|
tableName,
|
||||||
where: incomingWhere,
|
where,
|
||||||
})
|
})
|
||||||
|
|
||||||
return docs?.[0] || null
|
return docs?.[0] || null
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { FindVersions } from 'payload/database'
|
|||||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export const findVersions: FindVersions = async function findVersions(
|
export const findVersions: FindVersions = async function findVersions(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -25,7 +25,11 @@ export const findVersions: FindVersions = async function findVersions(
|
|||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||||
|
|
||||||
const tableName = `_${toSnakeCase(collection)}_v`
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collectionConfig,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
const fields = buildVersionCollectionFields(collectionConfig)
|
const fields = buildVersionCollectionFields(collectionConfig)
|
||||||
|
|
||||||
return findMany({
|
return findMany({
|
||||||
|
|||||||
@@ -47,20 +47,24 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
|
|||||||
name: 'postgres',
|
name: 'postgres',
|
||||||
|
|
||||||
// Postgres-specific
|
// Postgres-specific
|
||||||
|
blockTableNames: {},
|
||||||
drizzle: undefined,
|
drizzle: undefined,
|
||||||
enums: {},
|
enums: {},
|
||||||
fieldConstraints: {},
|
fieldConstraints: {},
|
||||||
idType,
|
idType,
|
||||||
|
localesSuffix: args.localesSuffix || '_locales',
|
||||||
logger: args.logger,
|
logger: args.logger,
|
||||||
pgSchema: undefined,
|
pgSchema: undefined,
|
||||||
pool: undefined,
|
pool: undefined,
|
||||||
poolOptions: args.pool,
|
poolOptions: args.pool,
|
||||||
push: args.push,
|
push: args.push,
|
||||||
relations: {},
|
relations: {},
|
||||||
|
relationshipsSuffix: args.relationshipsSuffix || '_rels',
|
||||||
schema: {},
|
schema: {},
|
||||||
schemaName: args.schemaName,
|
schemaName: args.schemaName,
|
||||||
sessions: {},
|
sessions: {},
|
||||||
tables: {},
|
tables: {},
|
||||||
|
versionsSuffix: args.versionsSuffix || '_v',
|
||||||
|
|
||||||
// DatabaseAdapter
|
// DatabaseAdapter
|
||||||
beginTransaction,
|
beginTransaction,
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
|||||||
|
|
||||||
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
|
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { buildTable } from './schema/build'
|
import { buildTable } from './schema/build'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export const init: Init = async function init(this: PostgresAdapter) {
|
export const init: Init = async function init(this: PostgresAdapter) {
|
||||||
if (this.schemaName) {
|
if (this.schemaName) {
|
||||||
@@ -25,7 +25,10 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||||
const tableName = toSnakeCase(collection.slug)
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collection,
|
||||||
|
})
|
||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
@@ -37,10 +40,15 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
fields: collection.fields,
|
fields: collection.fields,
|
||||||
tableName,
|
tableName,
|
||||||
timestamps: collection.timestamps,
|
timestamps: collection.timestamps,
|
||||||
|
versions: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (collection.versions) {
|
if (collection.versions) {
|
||||||
const versionsTableName = `_${tableName}_v`
|
const versionsTableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collection,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
const versionFields = buildVersionCollectionFields(collection)
|
const versionFields = buildVersionCollectionFields(collection)
|
||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
@@ -53,12 +61,13 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
fields: versionFields,
|
fields: versionFields,
|
||||||
tableName: versionsTableName,
|
tableName: versionsTableName,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
versions: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.payload.config.globals.forEach((global) => {
|
this.payload.config.globals.forEach((global) => {
|
||||||
const tableName = toSnakeCase(global.slug)
|
const tableName = getTableName({ adapter: this, config: global })
|
||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
@@ -70,10 +79,11 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
fields: global.fields,
|
fields: global.fields,
|
||||||
tableName,
|
tableName,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
|
versions: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (global.versions) {
|
if (global.versions) {
|
||||||
const versionsTableName = `_${tableName}_v`
|
const versionsTableName = getTableName({ adapter: this, config: global, versions: true })
|
||||||
const versionFields = buildVersionGlobalFields(global)
|
const versionFields = buildVersionGlobalFields(global)
|
||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
@@ -86,6 +96,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
fields: versionFields,
|
fields: versionFields,
|
||||||
tableName: versionsTableName,
|
tableName: versionsTableName,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
versions: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import { v4 as uuid } from 'uuid'
|
|||||||
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types'
|
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types'
|
||||||
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery'
|
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery'
|
||||||
|
|
||||||
|
import { getTableName } from '../schema/getTableName'
|
||||||
|
|
||||||
type Constraint = {
|
type Constraint = {
|
||||||
columnName: string
|
columnName: string
|
||||||
table: GenericTable | PgTableWithColumns<any>
|
table: GenericTable | PgTableWithColumns<any>
|
||||||
@@ -95,7 +97,7 @@ export const getTableColumnFromPath = ({
|
|||||||
field: {
|
field: {
|
||||||
name: 'id',
|
name: 'id',
|
||||||
type: adapter.idType === 'uuid' ? 'text' : 'number',
|
type: adapter.idType === 'uuid' ? 'text' : 'number',
|
||||||
} as TextField | NumberField,
|
} as NumberField | TextField,
|
||||||
table: adapter.tables[newTableName],
|
table: adapter.tables[newTableName],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +185,13 @@ export const getTableColumnFromPath = ({
|
|||||||
|
|
||||||
case 'group': {
|
case 'group': {
|
||||||
if (locale && field.localized && adapter.payload.config.localization) {
|
if (locale && field.localized && adapter.payload.config.localization) {
|
||||||
newTableName = `${tableName}_locales`
|
newTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: field,
|
||||||
|
locales: true,
|
||||||
|
parentTableName: tableName,
|
||||||
|
prefix: `${tableName}_`,
|
||||||
|
})
|
||||||
|
|
||||||
joins[tableName] = eq(
|
joins[tableName] = eq(
|
||||||
adapter.tables[tableName].id,
|
adapter.tables[tableName].id,
|
||||||
@@ -218,7 +226,12 @@ export const getTableColumnFromPath = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'array': {
|
case 'array': {
|
||||||
newTableName = `${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`
|
newTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: field,
|
||||||
|
parentTableName: `${tableName}_${tableNameSuffix}`,
|
||||||
|
prefix: `${tableName}_${tableNameSuffix}`,
|
||||||
|
})
|
||||||
constraintPath = `${constraintPath}${field.name}.%.`
|
constraintPath = `${constraintPath}${field.name}.%.`
|
||||||
if (locale && field.localized && adapter.payload.config.localization) {
|
if (locale && field.localized && adapter.payload.config.localization) {
|
||||||
joins[newTableName] = and(
|
joins[newTableName] = and(
|
||||||
@@ -265,7 +278,12 @@ export const getTableColumnFromPath = ({
|
|||||||
const blockTypes = Array.isArray(value) ? value : [value]
|
const blockTypes = Array.isArray(value) ? value : [value]
|
||||||
blockTypes.forEach((blockType) => {
|
blockTypes.forEach((blockType) => {
|
||||||
const block = field.blocks.find((block) => block.slug === blockType)
|
const block = field.blocks.find((block) => block.slug === blockType)
|
||||||
newTableName = `${tableName}_blocks_${toSnakeCase(block.slug)}`
|
newTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: block,
|
||||||
|
parentTableName: tableName,
|
||||||
|
prefix: `${tableName}_blocks_`,
|
||||||
|
})
|
||||||
joins[newTableName] = eq(
|
joins[newTableName] = eq(
|
||||||
adapter.tables[tableName].id,
|
adapter.tables[tableName].id,
|
||||||
adapter.tables[newTableName]._parentID,
|
adapter.tables[newTableName]._parentID,
|
||||||
@@ -285,7 +303,12 @@ export const getTableColumnFromPath = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasBlockField = field.blocks.some((block) => {
|
const hasBlockField = field.blocks.some((block) => {
|
||||||
newTableName = `${tableName}_blocks_${toSnakeCase(block.slug)}`
|
newTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: block,
|
||||||
|
parentTableName: tableName,
|
||||||
|
prefix: `${tableName}_blocks_`,
|
||||||
|
})
|
||||||
constraintPath = `${constraintPath}${field.name}.%.`
|
constraintPath = `${constraintPath}${field.name}.%.`
|
||||||
let result
|
let result
|
||||||
const blockConstraints = []
|
const blockConstraints = []
|
||||||
@@ -351,7 +374,7 @@ export const getTableColumnFromPath = ({
|
|||||||
case 'relationship':
|
case 'relationship':
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
let relationshipFields
|
let relationshipFields
|
||||||
const relationTableName = `${rootTableName}_rels`
|
const relationTableName = `${rootTableName}${adapter.relationshipsSuffix}`
|
||||||
const newCollectionPath = pathSegments.slice(1).join('.')
|
const newCollectionPath = pathSegments.slice(1).join('.')
|
||||||
const aliasRelationshipTableName = uuid()
|
const aliasRelationshipTableName = uuid()
|
||||||
const aliasRelationshipTable = alias(
|
const aliasRelationshipTable = alias(
|
||||||
@@ -359,23 +382,45 @@ export const getTableColumnFromPath = ({
|
|||||||
aliasRelationshipTableName,
|
aliasRelationshipTableName,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Join in the relationships table
|
if (locale && field.localized && adapter.payload.config.localization) {
|
||||||
joinAliases.push({
|
joinAliases.push({
|
||||||
condition: and(
|
condition: and(
|
||||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
eq(aliasRelationshipTable.locale, locale),
|
||||||
),
|
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||||
table: aliasRelationshipTable,
|
),
|
||||||
})
|
table: aliasRelationshipTable,
|
||||||
|
})
|
||||||
|
if (locale !== 'all') {
|
||||||
|
constraints.push({
|
||||||
|
columnName: 'locale',
|
||||||
|
table: aliasRelationshipTable,
|
||||||
|
value: locale,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Join in the relationships table
|
||||||
|
joinAliases.push({
|
||||||
|
condition: and(
|
||||||
|
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||||
|
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||||
|
),
|
||||||
|
table: aliasRelationshipTable,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
selectFields[`${relationTableName}.path`] = aliasRelationshipTable.path
|
selectFields[`${relationTableName}.path`] = aliasRelationshipTable.path
|
||||||
|
|
||||||
let newAliasTable
|
let newAliasTable
|
||||||
|
|
||||||
if (typeof field.relationTo === 'string') {
|
if (typeof field.relationTo === 'string') {
|
||||||
newTableName = `${toSnakeCase(field.relationTo)}`
|
const relationshipConfig = adapter.payload.collections[field.relationTo].config
|
||||||
|
newTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: relationshipConfig,
|
||||||
|
})
|
||||||
// parent to relationship join table
|
// parent to relationship join table
|
||||||
relationshipFields = adapter.payload.collections[field.relationTo].config.fields
|
relationshipFields = relationshipConfig.fields
|
||||||
|
|
||||||
newAliasTable = alias(adapter.tables[newTableName], toSnakeCase(uuid()))
|
newAliasTable = alias(adapter.tables[newTableName], toSnakeCase(uuid()))
|
||||||
|
|
||||||
@@ -394,7 +439,11 @@ export const getTableColumnFromPath = ({
|
|||||||
}
|
}
|
||||||
} else if (newCollectionPath === 'value') {
|
} else if (newCollectionPath === 'value') {
|
||||||
const tableColumnsNames = field.relationTo.map(
|
const tableColumnsNames = field.relationTo.map(
|
||||||
(relationTo) => `"${aliasRelationshipTableName}"."${toSnakeCase(relationTo)}_id"`,
|
(relationTo) =>
|
||||||
|
`"${aliasRelationshipTableName}"."${getTableName({
|
||||||
|
adapter,
|
||||||
|
config: adapter.payload.collections[relationTo].config,
|
||||||
|
})}_id"`,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
constraints,
|
constraints,
|
||||||
@@ -441,7 +490,7 @@ export const getTableColumnFromPath = ({
|
|||||||
if (field.localized && adapter.payload.config.localization) {
|
if (field.localized && adapter.payload.config.localization) {
|
||||||
// If localized, we go to localized table and set aliasTable to undefined
|
// If localized, we go to localized table and set aliasTable to undefined
|
||||||
// so it is not picked up below to be used as targetTable
|
// so it is not picked up below to be used as targetTable
|
||||||
newTableName = `${tableName}_locales`
|
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||||
|
|
||||||
const parentTable = aliasTable || adapter.tables[tableName]
|
const parentTable = aliasTable || adapter.tables[tableName]
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const sanitizeQueryValue = ({
|
|||||||
formattedValue = Number(val)
|
formattedValue = Number(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'date') {
|
if (field.type === 'date' && operator !== 'exists') {
|
||||||
if (typeof val === 'string') {
|
if (typeof val === 'string') {
|
||||||
formattedValue = new Date(val)
|
formattedValue = new Date(val)
|
||||||
if (Number.isNaN(Date.parse(formattedValue))) {
|
if (Number.isNaN(Date.parse(formattedValue))) {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
|||||||
|
|
||||||
import { type QueryDrafts, combineQueries } from 'payload/database'
|
import { type QueryDrafts, combineQueries } from 'payload/database'
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
|
|
||||||
export const queryDrafts: QueryDrafts = async function queryDrafts({
|
export const queryDrafts: QueryDrafts = async function queryDrafts({
|
||||||
collection,
|
collection,
|
||||||
@@ -17,7 +17,11 @@ export const queryDrafts: QueryDrafts = async function queryDrafts({
|
|||||||
where,
|
where,
|
||||||
}) {
|
}) {
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const tableName = `_${toSnakeCase(collection)}_v`
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collectionConfig,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
const fields = buildVersionCollectionFields(collectionConfig)
|
const fields = buildVersionCollectionFields(collectionConfig)
|
||||||
|
|
||||||
const combinedWhere = combineQueries({ latest: { equals: true } }, where)
|
const combinedWhere = combineQueries({ latest: { equals: true } }, where)
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import type { Field } from 'payload/types'
|
|||||||
import { relations } from 'drizzle-orm'
|
import { relations } from 'drizzle-orm'
|
||||||
import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core'
|
import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core'
|
||||||
import { fieldAffectsData } from 'payload/types'
|
import { fieldAffectsData } from 'payload/types'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types'
|
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types'
|
||||||
|
|
||||||
|
import { getTableName } from './getTableName'
|
||||||
import { parentIDColumnMap } from './parentIDColumnMap'
|
import { parentIDColumnMap } from './parentIDColumnMap'
|
||||||
import { setColumnID } from './setColumnID'
|
import { setColumnID } from './setColumnID'
|
||||||
import { traverseFields } from './traverseFields'
|
import { traverseFields } from './traverseFields'
|
||||||
@@ -35,6 +35,7 @@ type Args = {
|
|||||||
rootTableName?: string
|
rootTableName?: string
|
||||||
tableName: string
|
tableName: string
|
||||||
timestamps?: boolean
|
timestamps?: boolean
|
||||||
|
versions: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
@@ -59,6 +60,7 @@ export const buildTable = ({
|
|||||||
rootTableName: incomingRootTableName,
|
rootTableName: incomingRootTableName,
|
||||||
tableName,
|
tableName,
|
||||||
timestamps,
|
timestamps,
|
||||||
|
versions,
|
||||||
}: Args): Result => {
|
}: Args): Result => {
|
||||||
const rootTableName = incomingRootTableName || tableName
|
const rootTableName = incomingRootTableName || tableName
|
||||||
const columns: Record<string, PgColumnBuilder> = baseColumns
|
const columns: Record<string, PgColumnBuilder> = baseColumns
|
||||||
@@ -113,6 +115,7 @@ export const buildTable = ({
|
|||||||
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
|
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
|
||||||
rootTableIDColType: rootTableIDColType || idColType,
|
rootTableIDColType: rootTableIDColType || idColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
|
versions,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (timestamps) {
|
if (timestamps) {
|
||||||
@@ -147,7 +150,7 @@ export const buildTable = ({
|
|||||||
adapter.tables[tableName] = table
|
adapter.tables[tableName] = table
|
||||||
|
|
||||||
if (hasLocalizedField) {
|
if (hasLocalizedField) {
|
||||||
const localeTableName = `${tableName}_locales`
|
const localeTableName = `${tableName}${adapter.localesSuffix}`
|
||||||
localesColumns.id = serial('id').primaryKey()
|
localesColumns.id = serial('id').primaryKey()
|
||||||
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||||
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id')
|
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id')
|
||||||
@@ -288,11 +291,16 @@ export const buildTable = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
relationships.forEach((relationTo) => {
|
relationships.forEach((relationTo) => {
|
||||||
const formattedRelationTo = toSnakeCase(relationTo)
|
const relationshipConfig = adapter.payload.collections[relationTo].config
|
||||||
|
const formattedRelationTo = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: relationshipConfig,
|
||||||
|
throwValidationError: true,
|
||||||
|
})
|
||||||
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
|
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
|
||||||
const relatedCollectionCustomID = adapter.payload.collections[
|
const relatedCollectionCustomID = relationshipConfig.fields.find(
|
||||||
relationTo
|
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||||
].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id')
|
)
|
||||||
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
|
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
|
||||||
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
|
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
|
||||||
|
|
||||||
@@ -301,7 +309,7 @@ export const buildTable = ({
|
|||||||
).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' })
|
).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' })
|
||||||
})
|
})
|
||||||
|
|
||||||
const relationshipsTableName = `${tableName}_rels`
|
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
|
||||||
|
|
||||||
relationshipsTable = adapter.pgSchema.table(
|
relationshipsTable = adapter.pgSchema.table(
|
||||||
relationshipsTableName,
|
relationshipsTableName,
|
||||||
@@ -333,7 +341,11 @@ export const buildTable = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
relationships.forEach((relationTo) => {
|
relationships.forEach((relationTo) => {
|
||||||
const relatedTableName = toSnakeCase(relationTo)
|
const relatedTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: adapter.payload.collections[relationTo].config,
|
||||||
|
throwValidationError: true,
|
||||||
|
})
|
||||||
const idColumnName = `${relationTo}ID`
|
const idColumnName = `${relationTo}ID`
|
||||||
result[idColumnName] = one(adapter.tables[relatedTableName], {
|
result[idColumnName] = one(adapter.tables[relatedTableName], {
|
||||||
fields: [relationshipsTable[idColumnName]],
|
fields: [relationshipsTable[idColumnName]],
|
||||||
|
|||||||
75
packages/db-postgres/src/schema/getTableName.ts
Normal file
75
packages/db-postgres/src/schema/getTableName.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import type { DBIdentifierName } from 'payload/database'
|
||||||
|
|
||||||
|
import { APIError } from 'payload/errors'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
|
import type { PostgresAdapter } from '../types'
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
adapter: PostgresAdapter
|
||||||
|
/** The collection, global or field config **/
|
||||||
|
config: {
|
||||||
|
dbName?: DBIdentifierName
|
||||||
|
enumName?: DBIdentifierName
|
||||||
|
name?: string
|
||||||
|
slug?: string
|
||||||
|
}
|
||||||
|
/** Localized tables need to be given the locales suffix */
|
||||||
|
locales?: boolean
|
||||||
|
/** For nested tables passed for the user custom dbName functions to handle their own iterations */
|
||||||
|
parentTableName?: string
|
||||||
|
/** For sub tables (array for example) this needs to include the parentTableName */
|
||||||
|
prefix?: string
|
||||||
|
/** Adds the relationships suffix */
|
||||||
|
relationships?: boolean
|
||||||
|
/** For tables based on fields that could have both enumName and dbName (ie: select with hasMany), default: 'dbName' */
|
||||||
|
target?: 'dbName' | 'enumName'
|
||||||
|
throwValidationError?: boolean
|
||||||
|
/** Adds the versions suffix, should only be used on the base collection to duplicate suffixing */
|
||||||
|
versions?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to name database enums and tables
|
||||||
|
* Returns the table or enum name for a given entity
|
||||||
|
*/
|
||||||
|
export const getTableName = ({
|
||||||
|
adapter,
|
||||||
|
config: { name, slug },
|
||||||
|
config,
|
||||||
|
locales = false,
|
||||||
|
parentTableName,
|
||||||
|
prefix = '',
|
||||||
|
relationships = false,
|
||||||
|
target = 'dbName',
|
||||||
|
throwValidationError = false,
|
||||||
|
versions = false,
|
||||||
|
}: Args): string => {
|
||||||
|
let result: string
|
||||||
|
let custom = config[target]
|
||||||
|
|
||||||
|
if (!custom && target === 'enumName') {
|
||||||
|
custom = config['dbName']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom) {
|
||||||
|
result = typeof custom === 'function' ? custom({ tableName: parentTableName }) : custom
|
||||||
|
} else {
|
||||||
|
result = `${prefix}${toSnakeCase(name ?? slug)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locales) result = `${result}${adapter.localesSuffix}`
|
||||||
|
if (versions) result = `_${result}${adapter.versionsSuffix}`
|
||||||
|
if (relationships) result = `${result}${adapter.relationshipsSuffix}`
|
||||||
|
|
||||||
|
if (!throwValidationError) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.length > 63) {
|
||||||
|
throw new APIError(
|
||||||
|
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import type { GenericColumns, IDType, PostgresAdapter } from '../types'
|
|||||||
import { hasLocalesTable } from '../utilities/hasLocalesTable'
|
import { hasLocalesTable } from '../utilities/hasLocalesTable'
|
||||||
import { buildTable } from './build'
|
import { buildTable } from './build'
|
||||||
import { createIndex } from './createIndex'
|
import { createIndex } from './createIndex'
|
||||||
|
import { getTableName } from './getTableName'
|
||||||
import { idToUUID } from './idToUUID'
|
import { idToUUID } from './idToUUID'
|
||||||
import { parentIDColumnMap } from './parentIDColumnMap'
|
import { parentIDColumnMap } from './parentIDColumnMap'
|
||||||
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical'
|
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical'
|
||||||
@@ -53,6 +54,7 @@ type Args = {
|
|||||||
rootRelationsToBuild?: Map<string, string>
|
rootRelationsToBuild?: Map<string, string>
|
||||||
rootTableIDColType: string
|
rootTableIDColType: string
|
||||||
rootTableName: string
|
rootTableName: string
|
||||||
|
versions: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
@@ -86,7 +88,9 @@ export const traverseFields = ({
|
|||||||
rootRelationsToBuild,
|
rootRelationsToBuild,
|
||||||
rootTableIDColType,
|
rootTableIDColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
|
versions,
|
||||||
}: Args): Result => {
|
}: Args): Result => {
|
||||||
|
const throwValidationError = true
|
||||||
let hasLocalizedField = false
|
let hasLocalizedField = false
|
||||||
let hasLocalizedRelationshipField = false
|
let hasLocalizedRelationshipField = false
|
||||||
let hasManyTextField: 'index' | boolean = false
|
let hasManyTextField: 'index' | boolean = false
|
||||||
@@ -217,7 +221,15 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
case 'radio':
|
case 'radio':
|
||||||
case 'select': {
|
case 'select': {
|
||||||
const enumName = `enum_${newTableName}_${toSnakeCase(field.name)}`
|
const enumName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: field,
|
||||||
|
parentTableName: newTableName,
|
||||||
|
prefix: `enum_${newTableName}_`,
|
||||||
|
target: 'enumName',
|
||||||
|
throwValidationError,
|
||||||
|
versions,
|
||||||
|
})
|
||||||
|
|
||||||
adapter.enums[enumName] = pgEnum(
|
adapter.enums[enumName] = pgEnum(
|
||||||
enumName,
|
enumName,
|
||||||
@@ -231,7 +243,14 @@ export const traverseFields = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (field.type === 'select' && field.hasMany) {
|
if (field.type === 'select' && field.hasMany) {
|
||||||
const selectTableName = `${newTableName}_${toSnakeCase(field.name)}`
|
const selectTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: field,
|
||||||
|
parentTableName: newTableName,
|
||||||
|
prefix: `${newTableName}_`,
|
||||||
|
throwValidationError,
|
||||||
|
versions,
|
||||||
|
})
|
||||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||||
order: integer('order').notNull(),
|
order: integer('order').notNull(),
|
||||||
parent: parentIDColumnMap[parentIDColType]('parent_id')
|
parent: parentIDColumnMap[parentIDColType]('parent_id')
|
||||||
@@ -266,6 +285,7 @@ export const traverseFields = ({
|
|||||||
disableUnique,
|
disableUnique,
|
||||||
fields: [],
|
fields: [],
|
||||||
tableName: selectTableName,
|
tableName: selectTableName,
|
||||||
|
versions,
|
||||||
})
|
})
|
||||||
|
|
||||||
relationsToBuild.set(fieldName, selectTableName)
|
relationsToBuild.set(fieldName, selectTableName)
|
||||||
@@ -296,7 +316,13 @@ export const traverseFields = ({
|
|||||||
case 'array': {
|
case 'array': {
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
|
|
||||||
const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`
|
const arrayTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: field,
|
||||||
|
parentTableName: newTableName,
|
||||||
|
prefix: `${newTableName}_`,
|
||||||
|
throwValidationError,
|
||||||
|
})
|
||||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||||
_order: integer('_order').notNull(),
|
_order: integer('_order').notNull(),
|
||||||
_parentID: parentIDColumnMap[parentIDColType]('_parent_id')
|
_parentID: parentIDColumnMap[parentIDColType]('_parent_id')
|
||||||
@@ -334,6 +360,7 @@ export const traverseFields = ({
|
|||||||
rootTableIDColType,
|
rootTableIDColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
tableName: arrayTableName,
|
tableName: arrayTableName,
|
||||||
|
versions,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (subHasManyTextField) {
|
if (subHasManyTextField) {
|
||||||
@@ -356,7 +383,7 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalesTable(field.fields)) {
|
if (hasLocalesTable(field.fields)) {
|
||||||
result._locales = many(adapter.tables[`${arrayTableName}_locales`])
|
result._locales = many(adapter.tables[`${arrayTableName}${adapter.localesSuffix}`])
|
||||||
}
|
}
|
||||||
|
|
||||||
subRelationsToBuild.forEach((val, key) => {
|
subRelationsToBuild.forEach((val, key) => {
|
||||||
@@ -375,7 +402,13 @@ export const traverseFields = ({
|
|||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
|
|
||||||
field.blocks.forEach((block) => {
|
field.blocks.forEach((block) => {
|
||||||
const blockTableName = `${rootTableName}_blocks_${toSnakeCase(block.slug)}`
|
const blockTableName = getTableName({
|
||||||
|
adapter,
|
||||||
|
config: block,
|
||||||
|
parentTableName: rootTableName,
|
||||||
|
prefix: `${rootTableName}_blocks_`,
|
||||||
|
throwValidationError,
|
||||||
|
})
|
||||||
if (!adapter.tables[blockTableName]) {
|
if (!adapter.tables[blockTableName]) {
|
||||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||||
_order: integer('_order').notNull(),
|
_order: integer('_order').notNull(),
|
||||||
@@ -416,6 +449,7 @@ export const traverseFields = ({
|
|||||||
rootTableIDColType,
|
rootTableIDColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
tableName: blockTableName,
|
tableName: blockTableName,
|
||||||
|
versions,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (subHasManyTextField) {
|
if (subHasManyTextField) {
|
||||||
@@ -439,7 +473,9 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalesTable(block.fields)) {
|
if (hasLocalesTable(block.fields)) {
|
||||||
result._locales = many(adapter.tables[`${blockTableName}_locales`])
|
result._locales = many(
|
||||||
|
adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
subRelationsToBuild.forEach((val, key) => {
|
subRelationsToBuild.forEach((val, key) => {
|
||||||
@@ -451,7 +487,7 @@ export const traverseFields = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
adapter.relations[`relations_${blockTableName}`] = blockTableRelations
|
adapter.relations[`relations_${blockTableName}`] = blockTableRelations
|
||||||
} else if (process.env.NODE_ENV !== 'production') {
|
} else if (process.env.NODE_ENV !== 'production' && !versions) {
|
||||||
validateExistingBlockIsIdentical({
|
validateExistingBlockIsIdentical({
|
||||||
block,
|
block,
|
||||||
localized: field.localized,
|
localized: field.localized,
|
||||||
@@ -459,7 +495,7 @@ export const traverseFields = ({
|
|||||||
table: adapter.tables[blockTableName],
|
table: adapter.tables[blockTableName],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName
|
||||||
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
|
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -498,6 +534,7 @@ export const traverseFields = ({
|
|||||||
rootRelationsToBuild,
|
rootRelationsToBuild,
|
||||||
rootTableIDColType,
|
rootTableIDColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
|
versions,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (groupHasLocalizedField) hasLocalizedField = true
|
if (groupHasLocalizedField) hasLocalizedField = true
|
||||||
@@ -540,6 +577,7 @@ export const traverseFields = ({
|
|||||||
rootRelationsToBuild,
|
rootRelationsToBuild,
|
||||||
rootTableIDColType,
|
rootTableIDColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
|
versions,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (groupHasLocalizedField) hasLocalizedField = true
|
if (groupHasLocalizedField) hasLocalizedField = true
|
||||||
@@ -583,6 +621,7 @@ export const traverseFields = ({
|
|||||||
rootRelationsToBuild,
|
rootRelationsToBuild,
|
||||||
rootTableIDColType,
|
rootTableIDColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
|
versions,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (tabHasLocalizedField) hasLocalizedField = true
|
if (tabHasLocalizedField) hasLocalizedField = true
|
||||||
@@ -626,6 +665,7 @@ export const traverseFields = ({
|
|||||||
rootRelationsToBuild,
|
rootRelationsToBuild,
|
||||||
rootTableIDColType,
|
rootTableIDColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
|
versions,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (rowHasLocalizedField) hasLocalizedField = true
|
if (rowHasLocalizedField) hasLocalizedField = true
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type Args = {
|
|||||||
data: Record<string, unknown>[]
|
data: Record<string, unknown>[]
|
||||||
field: BlockField
|
field: BlockField
|
||||||
locale?: string
|
locale?: string
|
||||||
texts: Record<string, unknown>[]
|
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
path: string
|
path: string
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
@@ -26,6 +25,7 @@ type Args = {
|
|||||||
selects: {
|
selects: {
|
||||||
[tableName: string]: Record<string, unknown>[]
|
[tableName: string]: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
export const transformBlocks = ({
|
export const transformBlocks = ({
|
||||||
adapter,
|
adapter,
|
||||||
@@ -35,12 +35,12 @@ export const transformBlocks = ({
|
|||||||
data,
|
data,
|
||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
}: Args) => {
|
}: Args) => {
|
||||||
data.forEach((blockRow, i) => {
|
data.forEach((blockRow, i) => {
|
||||||
if (typeof blockRow.blockType !== 'string') return
|
if (typeof blockRow.blockType !== 'string') return
|
||||||
@@ -86,7 +86,6 @@ export const transformBlocks = ({
|
|||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields: matchedBlock.fields,
|
fields: matchedBlock.fields,
|
||||||
locales: newRow.locales,
|
locales: newRow.locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName: blockTableName,
|
parentTableName: blockTableName,
|
||||||
path: `${path || ''}${field.name}.${i}.`,
|
path: `${path || ''}${field.name}.${i}.`,
|
||||||
@@ -94,6 +93,7 @@ export const transformBlocks = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row: newRow.row,
|
row: newRow.row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
|
|
||||||
blocks[blockType].push(newRow)
|
blocks[blockType].push(newRow)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import toSnakeCase from 'to-snake-case'
|
|||||||
import type { PostgresAdapter } from '../../types'
|
import type { PostgresAdapter } from '../../types'
|
||||||
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types'
|
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types'
|
||||||
|
|
||||||
|
import { getTableName } from '../../schema/getTableName'
|
||||||
import { isArrayOfRows } from '../../utilities/isArrayOfRows'
|
import { isArrayOfRows } from '../../utilities/isArrayOfRows'
|
||||||
import { transformArray } from './array'
|
import { transformArray } from './array'
|
||||||
import { transformBlocks } from './blocks'
|
import { transformBlocks } from './blocks'
|
||||||
@@ -45,7 +46,6 @@ type Args = {
|
|||||||
locales: {
|
locales: {
|
||||||
[locale: string]: Record<string, unknown>
|
[locale: string]: Record<string, unknown>
|
||||||
}
|
}
|
||||||
texts: Record<string, unknown>[]
|
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
/**
|
/**
|
||||||
* This is the name of the parent table
|
* This is the name of the parent table
|
||||||
@@ -58,6 +58,7 @@ type Args = {
|
|||||||
selects: {
|
selects: {
|
||||||
[tableName: string]: Record<string, unknown>[]
|
[tableName: string]: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const traverseFields = ({
|
export const traverseFields = ({
|
||||||
@@ -73,7 +74,6 @@ export const traverseFields = ({
|
|||||||
fields,
|
fields,
|
||||||
forcedLocale,
|
forcedLocale,
|
||||||
locales,
|
locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path,
|
path,
|
||||||
@@ -81,6 +81,7 @@ export const traverseFields = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
}: Args) => {
|
}: Args) => {
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
let columnName = ''
|
let columnName = ''
|
||||||
@@ -88,7 +89,12 @@ export const traverseFields = ({
|
|||||||
let fieldData: unknown
|
let fieldData: unknown
|
||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}`
|
columnName = `${columnPrefix || ''}${getTableName({
|
||||||
|
adapter,
|
||||||
|
config: field,
|
||||||
|
// do not pass columnPrefix here because it is required and custom dbNames also need it
|
||||||
|
prefix: '',
|
||||||
|
})}`
|
||||||
fieldName = `${fieldPrefix || ''}${field.name}`
|
fieldName = `${fieldPrefix || ''}${field.name}`
|
||||||
fieldData = data[field.name]
|
fieldData = data[field.name]
|
||||||
}
|
}
|
||||||
@@ -111,12 +117,12 @@ export const traverseFields = ({
|
|||||||
data: localeData,
|
data: localeData,
|
||||||
field,
|
field,
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
|
|
||||||
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
||||||
@@ -132,12 +138,12 @@ export const traverseFields = ({
|
|||||||
blocksToDelete,
|
blocksToDelete,
|
||||||
data: data[field.name],
|
data: data[field.name],
|
||||||
field,
|
field,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
|
|
||||||
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
||||||
@@ -147,8 +153,8 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'blocks') {
|
if (field.type === 'blocks') {
|
||||||
field.blocks.forEach(({ slug }) => {
|
field.blocks.forEach((block) => {
|
||||||
blocksToDelete.add(toSnakeCase(slug))
|
blocksToDelete.add(getTableName({ adapter, config: block }))
|
||||||
})
|
})
|
||||||
|
|
||||||
if (field.localized) {
|
if (field.localized) {
|
||||||
@@ -163,12 +169,12 @@ export const traverseFields = ({
|
|||||||
data: localeData,
|
data: localeData,
|
||||||
field,
|
field,
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -181,12 +187,12 @@ export const traverseFields = ({
|
|||||||
blocksToDelete,
|
blocksToDelete,
|
||||||
data: fieldData,
|
data: fieldData,
|
||||||
field,
|
field,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +216,6 @@ export const traverseFields = ({
|
|||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
forcedLocale: localeKey,
|
forcedLocale: localeKey,
|
||||||
locales,
|
locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${field.name}.`,
|
path: `${path || ''}${field.name}.`,
|
||||||
@@ -218,6 +223,7 @@ export const traverseFields = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -233,7 +239,6 @@ export const traverseFields = ({
|
|||||||
fieldPrefix: `${fieldName}_`,
|
fieldPrefix: `${fieldName}_`,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locales,
|
locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${field.name}.`,
|
path: `${path || ''}${field.name}.`,
|
||||||
@@ -241,6 +246,7 @@ export const traverseFields = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +273,6 @@ export const traverseFields = ({
|
|||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
forcedLocale: localeKey,
|
forcedLocale: localeKey,
|
||||||
locales,
|
locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${tab.name}.`,
|
path: `${path || ''}${tab.name}.`,
|
||||||
@@ -275,6 +280,7 @@ export const traverseFields = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -290,7 +296,6 @@ export const traverseFields = ({
|
|||||||
fieldPrefix: `${fieldPrefix || ''}${tab.name}_`,
|
fieldPrefix: `${fieldPrefix || ''}${tab.name}_`,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
locales,
|
locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${tab.name}.`,
|
path: `${path || ''}${tab.name}.`,
|
||||||
@@ -298,6 +303,7 @@ export const traverseFields = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,7 +320,6 @@ export const traverseFields = ({
|
|||||||
fieldPrefix,
|
fieldPrefix,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
locales,
|
locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path,
|
path,
|
||||||
@@ -322,6 +327,7 @@ export const traverseFields = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -340,7 +346,6 @@ export const traverseFields = ({
|
|||||||
fieldPrefix,
|
fieldPrefix,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locales,
|
locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path,
|
path,
|
||||||
@@ -348,6 +353,7 @@ export const traverseFields = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +494,11 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
const valuesToTransform: { localeKey?: string; ref: unknown; value: unknown }[] = []
|
const valuesToTransform: {
|
||||||
|
localeKey?: string
|
||||||
|
ref: unknown
|
||||||
|
value: unknown
|
||||||
|
}[] = []
|
||||||
|
|
||||||
if (field.localized) {
|
if (field.localized) {
|
||||||
if (typeof fieldData === 'object' && fieldData !== null) {
|
if (typeof fieldData === 'object' && fieldData !== null) {
|
||||||
|
|||||||
@@ -23,12 +23,15 @@ import type { Pool, PoolConfig } from 'pg'
|
|||||||
export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
|
export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
|
||||||
|
|
||||||
export type Args = {
|
export type Args = {
|
||||||
|
localesSuffix?: string
|
||||||
idType?: 'serial' | 'uuid'
|
idType?: 'serial' | 'uuid'
|
||||||
logger?: DrizzleConfig['logger']
|
logger?: DrizzleConfig['logger']
|
||||||
migrationDir?: string
|
migrationDir?: string
|
||||||
pool: PoolConfig
|
pool: PoolConfig
|
||||||
push?: boolean
|
push?: boolean
|
||||||
schemaName?: string
|
schemaName?: string
|
||||||
|
relationshipsSuffix?: string
|
||||||
|
versionsSuffix?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GenericColumn = PgColumn<
|
export type GenericColumn = PgColumn<
|
||||||
@@ -58,6 +61,10 @@ export type DrizzleTransaction = PgTransaction<
|
|||||||
>
|
>
|
||||||
|
|
||||||
export type PostgresAdapter = BaseDatabaseAdapter & {
|
export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||||
|
/**
|
||||||
|
* Used internally to map the block name to the table name
|
||||||
|
*/
|
||||||
|
blockTableNames: Record<string, string>
|
||||||
drizzle: DrizzleDB
|
drizzle: DrizzleDB
|
||||||
enums: Record<string, GenericEnum>
|
enums: Record<string, GenericEnum>
|
||||||
/**
|
/**
|
||||||
@@ -66,12 +73,14 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
|||||||
*/
|
*/
|
||||||
fieldConstraints: Record<string, Record<string, string>>
|
fieldConstraints: Record<string, Record<string, string>>
|
||||||
idType: Args['idType']
|
idType: Args['idType']
|
||||||
|
localesSuffix?: string
|
||||||
logger: DrizzleConfig['logger']
|
logger: DrizzleConfig['logger']
|
||||||
pgSchema?: { table: PgTableFn } | PgSchema
|
pgSchema?: { table: PgTableFn } | PgSchema
|
||||||
pool: Pool
|
pool: Pool
|
||||||
poolOptions: Args['pool']
|
poolOptions: Args['pool']
|
||||||
push: boolean
|
push: boolean
|
||||||
relations: Record<string, GenericRelation>
|
relations: Record<string, GenericRelation>
|
||||||
|
relationshipsSuffix?: string
|
||||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||||
schemaName?: Args['schemaName']
|
schemaName?: Args['schemaName']
|
||||||
sessions: {
|
sessions: {
|
||||||
@@ -82,14 +91,21 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tables: Record<string, GenericTable | PgTableWithColumns<any>>
|
tables: Record<string, GenericTable | PgTableWithColumns<any>>
|
||||||
|
versionsSuffix?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
|
export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
|
||||||
|
|
||||||
export type PostgresAdapterResult = (args: { payload: Payload }) => PostgresAdapter
|
export type PostgresAdapterResult = (args: { payload: Payload }) => PostgresAdapter
|
||||||
|
|
||||||
export type MigrateUpArgs = { payload: Payload; req?: Partial<PayloadRequest> }
|
export type MigrateUpArgs = {
|
||||||
export type MigrateDownArgs = { payload: Payload; req?: Partial<PayloadRequest> }
|
payload: Payload
|
||||||
|
req?: Partial<PayloadRequest>
|
||||||
|
}
|
||||||
|
export type MigrateDownArgs = {
|
||||||
|
payload: Payload
|
||||||
|
req?: Partial<PayloadRequest>
|
||||||
|
}
|
||||||
|
|
||||||
declare module 'payload' {
|
declare module 'payload' {
|
||||||
export interface DatabaseAdapter
|
export interface DatabaseAdapter
|
||||||
@@ -98,9 +114,11 @@ declare module 'payload' {
|
|||||||
drizzle: DrizzleDB
|
drizzle: DrizzleDB
|
||||||
enums: Record<string, GenericEnum>
|
enums: Record<string, GenericEnum>
|
||||||
fieldConstraints: Record<string, Record<string, string>>
|
fieldConstraints: Record<string, Record<string, string>>
|
||||||
|
localeSuffix?: string
|
||||||
pool: Pool
|
pool: Pool
|
||||||
push: boolean
|
push: boolean
|
||||||
relations: Record<string, GenericRelation>
|
relations: Record<string, GenericRelation>
|
||||||
|
relationshipsSuffix?: string
|
||||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||||
sessions: {
|
sessions: {
|
||||||
[id: string]: {
|
[id: string]: {
|
||||||
@@ -110,5 +128,6 @@ declare module 'payload' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tables: Record<string, GenericTable>
|
tables: Record<string, GenericTable>
|
||||||
|
versionsSuffix?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import type { UpdateOne } from 'payload/database'
|
|||||||
|
|
||||||
import toSnakeCase from 'to-snake-case'
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
|
import type { ChainedMethods } from './find/chainMethods'
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import buildQuery from './queries/buildQuery'
|
import buildQuery from './queries/buildQuery'
|
||||||
import { selectDistinct } from './queries/selectDistinct'
|
import { selectDistinct } from './queries/selectDistinct'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export const updateOne: UpdateOne = async function updateOne(
|
export const updateOne: UpdateOne = async function updateOne(
|
||||||
@@ -14,7 +16,10 @@ export const updateOne: UpdateOne = async function updateOne(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collection = this.payload.collections[collectionSlug].config
|
const collection = this.payload.collections[collectionSlug].config
|
||||||
const tableName = toSnakeCase(collectionSlug)
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collection,
|
||||||
|
})
|
||||||
const whereToUse = whereArg || { id: { equals: id } }
|
const whereToUse = whereArg || { id: { equals: id } }
|
||||||
let idToUpdate = id
|
let idToUpdate = id
|
||||||
|
|
||||||
@@ -49,7 +54,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
|||||||
fields: collection.fields,
|
fields: collection.fields,
|
||||||
operation: 'update',
|
operation: 'update',
|
||||||
req,
|
req,
|
||||||
tableName: toSnakeCase(collectionSlug),
|
tableName,
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import type { UpdateGlobalArgs } from 'payload/database'
|
import type { UpdateGlobalArgs } from 'payload/database'
|
||||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function updateGlobal<T extends TypeWithID>(
|
export async function updateGlobal<T extends TypeWithID>(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
{ data, req = {} as PayloadRequest, slug }: UpdateGlobalArgs,
|
{ slug, data, req = {} as PayloadRequest }: UpdateGlobalArgs,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||||
const tableName = toSnakeCase(slug)
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: globalConfig,
|
||||||
|
})
|
||||||
|
|
||||||
const existingGlobal = await db.query[tableName].findFirst({})
|
const existingGlobal = await db.query[tableName].findFirst({})
|
||||||
|
|
||||||
@@ -23,8 +25,8 @@ export async function updateGlobal<T extends TypeWithID>(
|
|||||||
data,
|
data,
|
||||||
db,
|
db,
|
||||||
fields: globalConfig.fields,
|
fields: globalConfig.fields,
|
||||||
tableName,
|
|
||||||
req,
|
req,
|
||||||
|
tableName,
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateGlobalVersionArgs } from 'payload/database'
|
|||||||
import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
import { buildVersionGlobalFields } from 'payload/versions'
|
import { buildVersionGlobalFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import buildQuery from './queries/buildQuery'
|
import buildQuery from './queries/buildQuery'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||||
@@ -25,7 +25,11 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
|||||||
({ slug }) => slug === global,
|
({ slug }) => slug === global,
|
||||||
)
|
)
|
||||||
const whereToUse = whereArg || { id: { equals: id } }
|
const whereToUse = whereArg || { id: { equals: id } }
|
||||||
const tableName = `_${toSnakeCase(global)}_v`
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: globalConfig,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
const fields = buildVersionGlobalFields(globalConfig)
|
const fields = buildVersionGlobalFields(globalConfig)
|
||||||
|
|
||||||
const { where } = await buildQuery({
|
const { where } = await buildQuery({
|
||||||
@@ -43,9 +47,9 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
|||||||
db,
|
db,
|
||||||
fields,
|
fields,
|
||||||
operation: 'update',
|
operation: 'update',
|
||||||
|
req,
|
||||||
tableName,
|
tableName,
|
||||||
where,
|
where,
|
||||||
req,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateVersionArgs } from 'payload/database'
|
|||||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
import toSnakeCase from 'to-snake-case'
|
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import buildQuery from './queries/buildQuery'
|
import buildQuery from './queries/buildQuery'
|
||||||
|
import { getTableName } from './schema/getTableName'
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function updateVersion<T extends TypeWithID>(
|
export async function updateVersion<T extends TypeWithID>(
|
||||||
@@ -23,7 +23,11 @@ export async function updateVersion<T extends TypeWithID>(
|
|||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const whereToUse = whereArg || { id: { equals: id } }
|
const whereToUse = whereArg || { id: { equals: id } }
|
||||||
const tableName = `_${toSnakeCase(collection)}_v`
|
const tableName = getTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: collectionConfig,
|
||||||
|
versions: true,
|
||||||
|
})
|
||||||
const fields = buildVersionCollectionFields(collectionConfig)
|
const fields = buildVersionCollectionFields(collectionConfig)
|
||||||
|
|
||||||
const { where } = await buildQuery({
|
const { where } = await buildQuery({
|
||||||
@@ -41,9 +45,9 @@ export async function updateVersion<T extends TypeWithID>(
|
|||||||
db,
|
db,
|
||||||
fields,
|
fields,
|
||||||
operation: 'update',
|
operation: 'update',
|
||||||
|
req,
|
||||||
tableName,
|
tableName,
|
||||||
where,
|
where,
|
||||||
req,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export const upsertRow = async <T extends TypeWithID>({
|
|||||||
// //////////////////////////////////
|
// //////////////////////////////////
|
||||||
|
|
||||||
if (localesToInsert.length > 0) {
|
if (localesToInsert.length > 0) {
|
||||||
const localeTable = adapter.tables[`${tableName}_locales`]
|
const localeTable = adapter.tables[`${tableName}${adapter.localesSuffix}`]
|
||||||
|
|
||||||
if (operation === 'update') {
|
if (operation === 'update') {
|
||||||
await db.delete(localeTable).where(eq(localeTable._parentID, insertedRow.id))
|
await db.delete(localeTable).where(eq(localeTable._parentID, insertedRow.id))
|
||||||
@@ -151,7 +151,7 @@ export const upsertRow = async <T extends TypeWithID>({
|
|||||||
// INSERT RELATIONSHIPS
|
// INSERT RELATIONSHIPS
|
||||||
// //////////////////////////////////
|
// //////////////////////////////////
|
||||||
|
|
||||||
const relationshipsTableName = `${tableName}_rels`
|
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
|
||||||
|
|
||||||
if (operation === 'update') {
|
if (operation === 'update') {
|
||||||
await deleteExistingRowsByPath({
|
await deleteExistingRowsByPath({
|
||||||
@@ -224,15 +224,16 @@ export const upsertRow = async <T extends TypeWithID>({
|
|||||||
|
|
||||||
if (operation === 'update') {
|
if (operation === 'update') {
|
||||||
for (const blockName of rowToInsert.blocksToDelete) {
|
for (const blockName of rowToInsert.blocksToDelete) {
|
||||||
const blockTableName = `${tableName}_blocks_${blockName}`
|
const blockTableName = adapter.blockTableNames[`${tableName}.${blockName}`]
|
||||||
const blockTable = adapter.tables[blockTableName]
|
const blockTable = adapter.tables[blockTableName]
|
||||||
await db.delete(blockTable).where(eq(blockTable._parentID, insertedRow.id))
|
await db.delete(blockTable).where(eq(blockTable._parentID, insertedRow.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [blockName, blockRows] of Object.entries(blocksToInsert)) {
|
for (const [blockName, blockRows] of Object.entries(blocksToInsert)) {
|
||||||
|
const blockTableName = adapter.blockTableNames[`${tableName}.${blockName}`]
|
||||||
insertedBlockRows[blockName] = await db
|
insertedBlockRows[blockName] = await db
|
||||||
.insert(adapter.tables[`${tableName}_blocks_${blockName}`])
|
.insert(adapter.tables[blockTableName])
|
||||||
.values(blockRows.map(({ row }) => row))
|
.values(blockRows.map(({ row }) => row))
|
||||||
.returning()
|
.returning()
|
||||||
|
|
||||||
@@ -259,7 +260,7 @@ export const upsertRow = async <T extends TypeWithID>({
|
|||||||
|
|
||||||
if (blockLocaleRowsToInsert.length > 0) {
|
if (blockLocaleRowsToInsert.length > 0) {
|
||||||
await db
|
await db
|
||||||
.insert(adapter.tables[`${tableName}_blocks_${blockName}_locales`])
|
.insert(adapter.tables[`${blockTableName}${adapter.localesSuffix}`])
|
||||||
.values(blockLocaleRowsToInsert)
|
.values(blockLocaleRowsToInsert)
|
||||||
.returning()
|
.returning()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const insertArrays = async ({ adapter, arrays, db, parentRows }: Args): P
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Insert locale rows
|
// Insert locale rows
|
||||||
if (adapter.tables[`${tableName}_locales`] && row.locales.length > 0) {
|
if (adapter.tables[`${tableName}${adapter.localesSuffix}`] && row.locales.length > 0) {
|
||||||
if (!row.locales[0]._parentID) {
|
if (!row.locales[0]._parentID) {
|
||||||
row.locales = row.locales.map((localeRow, i) => {
|
row.locales = row.locales.map((localeRow, i) => {
|
||||||
if (typeof localeRow._getParentID === 'function') {
|
if (typeof localeRow._getParentID === 'function') {
|
||||||
@@ -81,7 +81,10 @@ export const insertArrays = async ({ adapter, arrays, db, parentRows }: Args): P
|
|||||||
return localeRow
|
return localeRow
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await db.insert(adapter.tables[`${tableName}_locales`]).values(row.locales).returning()
|
await db
|
||||||
|
.insert(adapter.tables[`${tableName}${adapter.localesSuffix}`])
|
||||||
|
.values(row.locales)
|
||||||
|
.returning()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are sub arrays, call this function recursively
|
// If there are sub arrays, call this function recursively
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ type BaseArgs = {
|
|||||||
db: DrizzleDB
|
db: DrizzleDB
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
path?: string
|
path?: string
|
||||||
tableName: string
|
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
|
tableName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateArgs = BaseArgs & {
|
type CreateArgs = BaseArgs & {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
"version": "2.12.0",
|
"version": "2.12.1",
|
||||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import './index.scss'
|
|||||||
|
|
||||||
const baseClass = 'duplicate'
|
const baseClass = 'duplicate'
|
||||||
|
|
||||||
const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
const Duplicate: React.FC<Props> = ({ id, slug, collection }) => {
|
||||||
const { push } = useHistory()
|
const { push } = useHistory()
|
||||||
const modified = useFormModified()
|
const modified = useFormModified()
|
||||||
const { toggleModal } = useModal()
|
const { toggleModal } = useModal()
|
||||||
@@ -31,12 +31,15 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
|||||||
routes: { admin },
|
routes: { admin },
|
||||||
} = useConfig()
|
} = useConfig()
|
||||||
const [hasClicked, setHasClicked] = useState<boolean>(false)
|
const [hasClicked, setHasClicked] = useState<boolean>(false)
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false)
|
||||||
const { i18n, t } = useTranslation('general')
|
const { i18n, t } = useTranslation('general')
|
||||||
|
|
||||||
const modalSlug = `duplicate-${id}`
|
const modalSlug = `duplicate-${id}`
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
async (override = false) => {
|
async (override = false) => {
|
||||||
|
if (isSubmitting) return
|
||||||
|
setIsSubmitting(true)
|
||||||
setHasClicked(true)
|
setHasClicked(true)
|
||||||
|
|
||||||
if (modified && !override) {
|
if (modified && !override) {
|
||||||
@@ -144,6 +147,7 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setModified(false)
|
setModified(false)
|
||||||
|
setIsSubmitting(false)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
push({
|
push({
|
||||||
@@ -170,13 +174,17 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const confirm = useCallback(async () => {
|
const confirm = useCallback(async () => {
|
||||||
setHasClicked(false)
|
|
||||||
await handleClick(true)
|
await handleClick(true)
|
||||||
|
setHasClicked(false)
|
||||||
}, [handleClick])
|
}, [handleClick])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<PopupList.Button id="action-duplicate" onClick={() => handleClick(false)}>
|
<PopupList.Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
id="action-duplicate"
|
||||||
|
onClick={() => handleClick(false)}
|
||||||
|
>
|
||||||
{t('duplicate')}
|
{t('duplicate')}
|
||||||
</PopupList.Button>
|
</PopupList.Button>
|
||||||
{modified && hasClicked && (
|
{modified && hasClicked && (
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ type MenuButtonProps = {
|
|||||||
active?: boolean
|
active?: boolean
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
className?: string
|
className?: string
|
||||||
|
disabled?: boolean
|
||||||
id?: string
|
id?: string
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
to?: LinkProps['to']
|
to?: LinkProps['to']
|
||||||
@@ -36,6 +37,7 @@ export const Button: React.FC<MenuButtonProps> = ({
|
|||||||
active,
|
active,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
|
disabled,
|
||||||
onClick,
|
onClick,
|
||||||
to,
|
to,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -64,6 +66,7 @@ export const Button: React.FC<MenuButtonProps> = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classes}
|
className={classes}
|
||||||
|
disabled={disabled}
|
||||||
id={id}
|
id={id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
|
|||||||
@@ -9,11 +9,16 @@ import { useDocumentInfo } from '../../utilities/DocumentInfo'
|
|||||||
import { useLocale } from '../../utilities/Locale'
|
import { useLocale } from '../../utilities/Locale'
|
||||||
import RenderCustomComponent from '../../utilities/RenderCustomComponent'
|
import RenderCustomComponent from '../../utilities/RenderCustomComponent'
|
||||||
|
|
||||||
export type CustomPublishButtonProps = React.ComponentType<
|
export type CustomPublishButtonType = React.ComponentType<
|
||||||
DefaultPublishButtonProps & {
|
DefaultPublishButtonProps & {
|
||||||
DefaultButton: React.ComponentType<DefaultPublishButtonProps>
|
DefaultButton: React.ComponentType<DefaultPublishButtonProps>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
/**
|
||||||
|
* @deprecated Use `CustomPublishButtonType` instead - renamed from `CustomPublishButtonProps`
|
||||||
|
*/
|
||||||
|
export type CustomPublishButtonProps = CustomPublishButtonType
|
||||||
|
|
||||||
export type DefaultPublishButtonProps = {
|
export type DefaultPublishButtonProps = {
|
||||||
canPublish: boolean
|
canPublish: boolean
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
@@ -38,7 +43,7 @@ const DefaultPublishButton: React.FC<DefaultPublishButtonProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
CustomComponent?: CustomPublishButtonProps
|
CustomComponent?: CustomPublishButtonType
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Publish: React.FC<Props> = ({ CustomComponent }) => {
|
export const Publish: React.FC<Props> = ({ CustomComponent }) => {
|
||||||
|
|||||||
@@ -85,15 +85,15 @@
|
|||||||
|
|
||||||
html[data-theme='light'] {
|
html[data-theme='light'] {
|
||||||
.tooltip {
|
.tooltip {
|
||||||
background-color: var(--theme-error-250);
|
background-color: var(--theme-elevation-100);
|
||||||
color: var(--theme-error-750);
|
color: var(--theme-elevation-1000);
|
||||||
|
|
||||||
&--position-top:after {
|
&--position-top:after {
|
||||||
border-top-color: var(--theme-error-250);
|
border-top-color: var(--theme-elevation-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
&--position-bottom:after {
|
&--position-bottom:after {
|
||||||
border-bottom-color: var(--theme-error-250);
|
border-bottom-color: var(--theme-elevation-100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type { CustomPreviewButtonProps } from './PreviewButton'
|
export type { CustomPreviewButtonProps } from './PreviewButton'
|
||||||
export type { CustomPublishButtonProps } from './Publish'
|
export type { CustomPublishButtonProps, CustomPublishButtonType } from './Publish'
|
||||||
export type { CustomSaveButtonProps } from './Save'
|
export type { CustomSaveButtonProps } from './Save'
|
||||||
export type { CustomSaveDraftButtonProps } from './SaveDraft'
|
export type { CustomSaveDraftButtonProps } from './SaveDraft'
|
||||||
|
|||||||
@@ -1,9 +1,34 @@
|
|||||||
export const getSupportedDateLocale = (locale = 'enUS'): string => {
|
export const getSupportedDateLocale = (locale = 'enUS'): string => {
|
||||||
|
// Need to match our translation locales with the local codes of 'date-fns/locale to support date locales
|
||||||
const formattedLocales = {
|
const formattedLocales = {
|
||||||
|
ar: 'ar',
|
||||||
|
az: 'az',
|
||||||
|
bg: 'bg',
|
||||||
|
cs: 'cs',
|
||||||
|
de: 'de',
|
||||||
en: 'enUS',
|
en: 'enUS',
|
||||||
|
es: 'es',
|
||||||
|
fa: 'faIR',
|
||||||
|
fr: 'fr',
|
||||||
|
hr: 'hr',
|
||||||
|
hu: 'hu',
|
||||||
|
it: 'it',
|
||||||
|
ja: 'ja',
|
||||||
|
ko: 'ko',
|
||||||
my: 'enUS', // Burmese is not currently supported
|
my: 'enUS', // Burmese is not currently supported
|
||||||
|
nb: 'nb',
|
||||||
|
nl: 'nl',
|
||||||
|
pl: 'pl',
|
||||||
|
pt: 'pt',
|
||||||
|
ro: 'ro',
|
||||||
|
ru: 'ru',
|
||||||
|
sv: 'sv',
|
||||||
|
th: 'th',
|
||||||
|
tr: 'tr',
|
||||||
ua: 'uk',
|
ua: 'uk',
|
||||||
|
vi: 'vi',
|
||||||
zh: 'zhCN',
|
zh: 'zhCN',
|
||||||
|
zhTw: 'zhTW',
|
||||||
}
|
}
|
||||||
|
|
||||||
return formattedLocales[locale] || locale
|
return formattedLocales[locale] || locale
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const strategyBaseSchema = joi.object().keys({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const collectionSchema = joi.object().keys({
|
const collectionSchema = joi.object().keys({
|
||||||
|
slug: joi.string().required(),
|
||||||
access: joi.object({
|
access: joi.object({
|
||||||
admin: joi.func(),
|
admin: joi.func(),
|
||||||
create: joi.func(),
|
create: joi.func(),
|
||||||
@@ -117,6 +118,7 @@ const collectionSchema = joi.object().keys({
|
|||||||
joi.boolean(),
|
joi.boolean(),
|
||||||
),
|
),
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
custom: joi.object().pattern(joi.string(), joi.any()),
|
||||||
|
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
||||||
defaultSort: joi.string(),
|
defaultSort: joi.string(),
|
||||||
endpoints: endpointsSchema,
|
endpoints: endpointsSchema,
|
||||||
fields: joi.array(),
|
fields: joi.array(),
|
||||||
@@ -158,7 +160,6 @@ const collectionSchema = joi.object().keys({
|
|||||||
.alternatives()
|
.alternatives()
|
||||||
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
||||||
}),
|
}),
|
||||||
slug: joi.string().required(),
|
|
||||||
timestamps: joi.boolean(),
|
timestamps: joi.boolean(),
|
||||||
typescript: joi.object().keys({
|
typescript: joi.object().keys({
|
||||||
interface: joi.string(),
|
interface: joi.string(),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { DeepRequired } from 'ts-essentials'
|
|||||||
import type { GeneratedTypes } from '../../'
|
import type { GeneratedTypes } from '../../'
|
||||||
import type {
|
import type {
|
||||||
CustomPreviewButtonProps,
|
CustomPreviewButtonProps,
|
||||||
CustomPublishButtonProps,
|
CustomPublishButtonType,
|
||||||
CustomSaveButtonProps,
|
CustomSaveButtonProps,
|
||||||
CustomSaveDraftButtonProps,
|
CustomSaveDraftButtonProps,
|
||||||
} from '../../admin/components/elements/types'
|
} from '../../admin/components/elements/types'
|
||||||
@@ -21,6 +21,7 @@ import type {
|
|||||||
GeneratePreviewURL,
|
GeneratePreviewURL,
|
||||||
LivePreviewConfig,
|
LivePreviewConfig,
|
||||||
} from '../../config/types'
|
} from '../../config/types'
|
||||||
|
import type { DBIdentifierName } from '../../database/types'
|
||||||
import type { PayloadRequest, RequestContext } from '../../express/types'
|
import type { PayloadRequest, RequestContext } from '../../express/types'
|
||||||
import type { Field } from '../../fields/config/types'
|
import type { Field } from '../../fields/config/types'
|
||||||
import type { IncomingUploadType, Upload } from '../../uploads/types'
|
import type { IncomingUploadType, Upload } from '../../uploads/types'
|
||||||
@@ -105,7 +106,9 @@ export type BeforeReadHook<T extends TypeWithID = any> = (args: {
|
|||||||
collection: SanitizedCollectionConfig
|
collection: SanitizedCollectionConfig
|
||||||
context: RequestContext
|
context: RequestContext
|
||||||
doc: T
|
doc: T
|
||||||
query: { [key: string]: any }
|
query: {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
}) => any
|
}) => any
|
||||||
|
|
||||||
@@ -115,7 +118,9 @@ export type AfterReadHook<T extends TypeWithID = any> = (args: {
|
|||||||
context: RequestContext
|
context: RequestContext
|
||||||
doc: T
|
doc: T
|
||||||
findMany?: boolean
|
findMany?: boolean
|
||||||
query?: { [key: string]: any }
|
query?: {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
}) => any
|
}) => any
|
||||||
|
|
||||||
@@ -146,7 +151,10 @@ export type AfterErrorHook = (
|
|||||||
context: RequestContext,
|
context: RequestContext,
|
||||||
/** The collection which this hook is being run on. This is null if the AfterError hook was be added to the payload-wide config */
|
/** The collection which this hook is being run on. This is null if the AfterError hook was be added to the payload-wide config */
|
||||||
collection: SanitizedCollectionConfig | null,
|
collection: SanitizedCollectionConfig | null,
|
||||||
) => { response: any; status: number } | void
|
) => {
|
||||||
|
response: any
|
||||||
|
status: number
|
||||||
|
} | void
|
||||||
|
|
||||||
export type BeforeLoginHook<T extends TypeWithID = any> = (args: {
|
export type BeforeLoginHook<T extends TypeWithID = any> = (args: {
|
||||||
/** The collection which this hook is being run on */
|
/** The collection which this hook is being run on */
|
||||||
@@ -228,7 +236,7 @@ export type CollectionAdminOptions = {
|
|||||||
* Replaces the "Publish" button
|
* Replaces the "Publish" button
|
||||||
* + drafts must be enabled
|
* + drafts must be enabled
|
||||||
*/
|
*/
|
||||||
PublishButton?: CustomPublishButtonProps
|
PublishButton?: CustomPublishButtonType
|
||||||
/**
|
/**
|
||||||
* Replaces the "Save" button
|
* Replaces the "Save" button
|
||||||
* + drafts must be disabled
|
* + drafts must be disabled
|
||||||
@@ -333,8 +341,7 @@ export type CollectionAdminOptions = {
|
|||||||
useAsTitle?: string
|
useAsTitle?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Manage all aspects of a data collection */
|
export type BaseCollectionConfig = {
|
||||||
export type CollectionConfig = {
|
|
||||||
/**
|
/**
|
||||||
* Access control
|
* Access control
|
||||||
*/
|
*/
|
||||||
@@ -359,6 +366,11 @@ export type CollectionConfig = {
|
|||||||
auth?: IncomingAuthType | boolean
|
auth?: IncomingAuthType | boolean
|
||||||
/** Extension point to add your custom data. */
|
/** Extension point to add your custom data. */
|
||||||
custom?: Record<string, any>
|
custom?: Record<string, any>
|
||||||
|
/**
|
||||||
|
* Used to override the default naming of the database table or collection with your using a function or string
|
||||||
|
* @WARNING: If you change this property with existing data, you will need to handle the renaming of the table in your database or by using migrations
|
||||||
|
*/
|
||||||
|
dbName?: DBIdentifierName
|
||||||
/**
|
/**
|
||||||
* Default field to sort by in collection list view
|
* Default field to sort by in collection list view
|
||||||
*/
|
*/
|
||||||
@@ -427,6 +439,7 @@ export type CollectionConfig = {
|
|||||||
* @default false // disable uploads
|
* @default false // disable uploads
|
||||||
*/
|
*/
|
||||||
upload?: IncomingUploadType | boolean
|
upload?: IncomingUploadType | boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customize the handling of incoming file uploads
|
* Customize the handling of incoming file uploads
|
||||||
*
|
*
|
||||||
@@ -435,6 +448,9 @@ export type CollectionConfig = {
|
|||||||
versions?: IncomingCollectionVersions | boolean
|
versions?: IncomingCollectionVersions | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Manage all aspects of a data collection */
|
||||||
|
export type CollectionConfig = BaseCollectionConfig
|
||||||
|
|
||||||
export interface SanitizedCollectionConfig
|
export interface SanitizedCollectionConfig
|
||||||
extends Omit<
|
extends Omit<
|
||||||
DeepRequired<CollectionConfig>,
|
DeepRequired<CollectionConfig>,
|
||||||
|
|||||||
@@ -403,3 +403,10 @@ export type PaginatedDocs<T = any> = {
|
|||||||
totalDocs: number
|
totalDocs: number
|
||||||
totalPages: number
|
totalPages: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DBIdentifierName =
|
||||||
|
| ((Args: {
|
||||||
|
/** The name of the parent table when using relational DBs */
|
||||||
|
tableName?: string
|
||||||
|
}) => string)
|
||||||
|
| string
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export {
|
|||||||
CreateMigration,
|
CreateMigration,
|
||||||
CreateVersion,
|
CreateVersion,
|
||||||
CreateVersionArgs,
|
CreateVersionArgs,
|
||||||
|
DBIdentifierName,
|
||||||
DeleteMany,
|
DeleteMany,
|
||||||
DeleteManyArgs,
|
DeleteManyArgs,
|
||||||
DeleteOne,
|
DeleteOne,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export { FileData, ImageSize, IncomingUploadType } from '../uploads/types'
|
|||||||
|
|
||||||
export type {
|
export type {
|
||||||
CustomPublishButtonProps,
|
CustomPublishButtonProps,
|
||||||
|
CustomPublishButtonType,
|
||||||
CustomSaveButtonProps,
|
CustomSaveButtonProps,
|
||||||
CustomSaveDraftButtonProps,
|
CustomSaveDraftButtonProps,
|
||||||
} from './../admin/components/elements/types'
|
} from './../admin/components/elements/types'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Config } from '../../config/types'
|
import type { Config } from '../../config/types'
|
||||||
import type { Field } from './types'
|
import type { Block, Field } from './types'
|
||||||
|
|
||||||
import withCondition from '../../admin/components/forms/withCondition'
|
import withCondition from '../../admin/components/forms/withCondition'
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +18,7 @@ type Args = {
|
|||||||
config: Config
|
config: Config
|
||||||
existingFieldNames?: Set<string>
|
existingFieldNames?: Set<string>
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
|
sanitizedBlocksMap?: Record<string, Block>
|
||||||
/**
|
/**
|
||||||
* If not null, will validate that upload and relationship fields do not relate to a collection that is not in this array.
|
* If not null, will validate that upload and relationship fields do not relate to a collection that is not in this array.
|
||||||
* This validation will be skipped if validRelationships is null.
|
* This validation will be skipped if validRelationships is null.
|
||||||
@@ -29,6 +30,7 @@ export const sanitizeFields = ({
|
|||||||
config,
|
config,
|
||||||
existingFieldNames = new Set(),
|
existingFieldNames = new Set(),
|
||||||
fields,
|
fields,
|
||||||
|
sanitizedBlocksMap = {},
|
||||||
validRelationships,
|
validRelationships,
|
||||||
}: Args): Field[] => {
|
}: Args): Field[] => {
|
||||||
if (!fields) return []
|
if (!fields) return []
|
||||||
@@ -96,10 +98,16 @@ export const sanitizeFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'blocks' && field.blocks) {
|
if (field.type === 'blocks' && field.blocks) {
|
||||||
field.blocks = field.blocks.map((block) => ({
|
field.blocks = field.blocks.map((block) => {
|
||||||
...block,
|
// break recursion
|
||||||
fields: block.fields.concat(baseBlockFields),
|
if (!block.slug && Object.keys(block)[0]) {
|
||||||
}))
|
return sanitizedBlocksMap[Object.keys(block)[0]]
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
fields: block.fields.concat(baseBlockFields),
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'array' && field.fields) {
|
if (field.type === 'array' && field.fields) {
|
||||||
@@ -113,7 +121,7 @@ export const sanitizeFields = ({
|
|||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
if (existingFieldNames.has(field.name)) {
|
if (existingFieldNames.has(field.name)) {
|
||||||
throw new DuplicateFieldName(field.name)
|
throw new DuplicateFieldName(field.name)
|
||||||
} else if (!['id', 'blockName'].includes(field.name)) {
|
} else if (!['blockName', 'id'].includes(field.name)) {
|
||||||
existingFieldNames.add(field.name)
|
existingFieldNames.add(field.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,16 +177,29 @@ export const sanitizeFields = ({
|
|||||||
|
|
||||||
if ('blocks' in field && field.blocks) {
|
if ('blocks' in field && field.blocks) {
|
||||||
field.blocks = field.blocks.map((block) => {
|
field.blocks = field.blocks.map((block) => {
|
||||||
const unsanitizedBlock = { ...block }
|
if (sanitizedBlocksMap[block?.interfaceName || block.slug]) {
|
||||||
|
return sanitizedBlocksMap[block?.interfaceName || block.slug]
|
||||||
|
}
|
||||||
|
|
||||||
|
// break recursion
|
||||||
|
if (!block.slug && Object.keys(block)[0]) {
|
||||||
|
return sanitizedBlocksMap[Object.keys(block)[0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizedBlocksMap[block?.interfaceName || block.slug] = { ...block }
|
||||||
|
|
||||||
|
const unsanitizedBlock = sanitizedBlocksMap[block?.interfaceName || block.slug]
|
||||||
|
|
||||||
unsanitizedBlock.labels = !unsanitizedBlock.labels
|
unsanitizedBlock.labels = !unsanitizedBlock.labels
|
||||||
? formatLabels(unsanitizedBlock.slug)
|
? formatLabels(unsanitizedBlock.slug)
|
||||||
: unsanitizedBlock.labels
|
: unsanitizedBlock.labels
|
||||||
|
|
||||||
unsanitizedBlock.fields = sanitizeFields({
|
unsanitizedBlock.fields = sanitizeFields({
|
||||||
config,
|
config,
|
||||||
fields: block.fields,
|
|
||||||
validRelationships,
|
|
||||||
existingFieldNames: new Set(),
|
existingFieldNames: new Set(),
|
||||||
|
fields: block.fields,
|
||||||
|
sanitizedBlocksMap,
|
||||||
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|
||||||
return unsanitizedBlock
|
return unsanitizedBlock
|
||||||
|
|||||||
@@ -200,9 +200,11 @@ export const select = baseField.keys({
|
|||||||
isClearable: joi.boolean().default(false),
|
isClearable: joi.boolean().default(false),
|
||||||
isSortable: joi.boolean().default(false),
|
isSortable: joi.boolean().default(false),
|
||||||
}),
|
}),
|
||||||
|
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
||||||
defaultValue: joi
|
defaultValue: joi
|
||||||
.alternatives()
|
.alternatives()
|
||||||
.try(joi.string().allow(''), joi.array().items(joi.string().allow('')), joi.func()),
|
.try(joi.string().allow(''), joi.array().items(joi.string().allow('')), joi.func()),
|
||||||
|
enumName: joi.alternatives().try(joi.string(), joi.func()),
|
||||||
hasMany: joi.boolean().default(false),
|
hasMany: joi.boolean().default(false),
|
||||||
options: joi
|
options: joi
|
||||||
.array()
|
.array()
|
||||||
@@ -232,6 +234,7 @@ export const radio = baseField.keys({
|
|||||||
layout: joi.string().valid('vertical', 'horizontal'),
|
layout: joi.string().valid('vertical', 'horizontal'),
|
||||||
}),
|
}),
|
||||||
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
||||||
|
enumName: joi.alternatives().try(joi.string(), joi.func()),
|
||||||
options: joi
|
options: joi
|
||||||
.array()
|
.array()
|
||||||
.min(1)
|
.min(1)
|
||||||
@@ -309,6 +312,7 @@ export const array = baseField.keys({
|
|||||||
.default({}),
|
.default({}),
|
||||||
})
|
})
|
||||||
.default({}),
|
.default({}),
|
||||||
|
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
||||||
defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func()),
|
defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func()),
|
||||||
fields: joi.array().items(joi.link('#field')).required(),
|
fields: joi.array().items(joi.link('#field')).required(),
|
||||||
interfaceName: joi.string(),
|
interfaceName: joi.string(),
|
||||||
@@ -409,6 +413,7 @@ export const blocks = baseField.keys({
|
|||||||
joi.object({
|
joi.object({
|
||||||
slug: joi.string().required(),
|
slug: joi.string().required(),
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
custom: joi.object().pattern(joi.string(), joi.any()),
|
||||||
|
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
||||||
fields: joi.array().items(joi.link('#field')),
|
fields: joi.array().items(joi.link('#field')),
|
||||||
graphQL: joi.object().keys({
|
graphQL: joi.object().keys({
|
||||||
singularName: joi.string(),
|
singularName: joi.string(),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import type { RichTextAdapter } from '../../admin/components/forms/field-types/R
|
|||||||
import type { User } from '../../auth'
|
import type { User } from '../../auth'
|
||||||
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types'
|
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types'
|
||||||
import type { SanitizedConfig } from '../../config/types'
|
import type { SanitizedConfig } from '../../config/types'
|
||||||
|
import type { DBIdentifierName } from '../../database/types'
|
||||||
import type { PayloadRequest, RequestContext } from '../../express/types'
|
import type { PayloadRequest, RequestContext } from '../../express/types'
|
||||||
import type { SanitizedGlobalConfig } from '../../globals/config/types'
|
import type { SanitizedGlobalConfig } from '../../globals/config/types'
|
||||||
import type { Payload } from '../../payload'
|
import type { Payload } from '../../payload'
|
||||||
@@ -78,7 +79,11 @@ export type FieldAccess<T extends TypeWithID = any, P = any, U = any> = (args: {
|
|||||||
export type Condition<T extends TypeWithID = any, P = any> = (
|
export type Condition<T extends TypeWithID = any, P = any> = (
|
||||||
data: Partial<T>,
|
data: Partial<T>,
|
||||||
siblingData: Partial<P>,
|
siblingData: Partial<P>,
|
||||||
{ user }: { user: User },
|
{
|
||||||
|
user,
|
||||||
|
}: {
|
||||||
|
user: User
|
||||||
|
},
|
||||||
) => boolean
|
) => boolean
|
||||||
|
|
||||||
export type FilterOptionsProps<T = any> = {
|
export type FilterOptionsProps<T = any> = {
|
||||||
@@ -443,6 +448,14 @@ export type SelectField = FieldBase & {
|
|||||||
isClearable?: boolean
|
isClearable?: boolean
|
||||||
isSortable?: boolean
|
isSortable?: boolean
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Customize the SQL table name
|
||||||
|
*/
|
||||||
|
dbName?: DBIdentifierName
|
||||||
|
/**
|
||||||
|
* Customize the DB enum name
|
||||||
|
*/
|
||||||
|
enumName?: DBIdentifierName
|
||||||
hasMany?: boolean
|
hasMany?: boolean
|
||||||
options: Option[]
|
options: Option[]
|
||||||
type: 'select'
|
type: 'select'
|
||||||
@@ -492,7 +505,9 @@ type RelationshipAdmin = Admin & {
|
|||||||
}
|
}
|
||||||
export type PolymorphicRelationshipField = SharedRelationshipProperties & {
|
export type PolymorphicRelationshipField = SharedRelationshipProperties & {
|
||||||
admin?: RelationshipAdmin & {
|
admin?: RelationshipAdmin & {
|
||||||
sortOptions?: { [collectionSlug: string]: string }
|
sortOptions?: {
|
||||||
|
[collectionSlug: string]: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
relationTo: string[]
|
relationTo: string[]
|
||||||
}
|
}
|
||||||
@@ -541,6 +556,10 @@ export type ArrayField = FieldBase & {
|
|||||||
} & Admin['components']
|
} & Admin['components']
|
||||||
initCollapsed?: boolean | false
|
initCollapsed?: boolean | false
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Customize the SQL table name
|
||||||
|
*/
|
||||||
|
dbName?: DBIdentifierName
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
/** Customize generated GraphQL and Typescript schema names.
|
/** Customize generated GraphQL and Typescript schema names.
|
||||||
* By default it is bound to the collection.
|
* By default it is bound to the collection.
|
||||||
@@ -563,6 +582,14 @@ export type RadioField = FieldBase & {
|
|||||||
}
|
}
|
||||||
layout?: 'horizontal' | 'vertical'
|
layout?: 'horizontal' | 'vertical'
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Customize the SQL table name
|
||||||
|
*/
|
||||||
|
dbName?: DBIdentifierName
|
||||||
|
/**
|
||||||
|
* Customize the DB enum name
|
||||||
|
*/
|
||||||
|
enumName?: DBIdentifierName
|
||||||
options: Option[]
|
options: Option[]
|
||||||
type: 'radio'
|
type: 'radio'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
const globalSchema = joi
|
const globalSchema = joi
|
||||||
.object()
|
.object()
|
||||||
.keys({
|
.keys({
|
||||||
|
slug: joi.string().required(),
|
||||||
access: joi.object({
|
access: joi.object({
|
||||||
read: joi.func(),
|
read: joi.func(),
|
||||||
readVersions: joi.func(),
|
readVersions: joi.func(),
|
||||||
@@ -48,6 +49,7 @@ const globalSchema = joi
|
|||||||
preview: joi.func(),
|
preview: joi.func(),
|
||||||
}),
|
}),
|
||||||
custom: joi.object().pattern(joi.string(), joi.any()),
|
custom: joi.object().pattern(joi.string(), joi.any()),
|
||||||
|
dbName: joi.alternatives().try(joi.string(), joi.func()),
|
||||||
endpoints: endpointsSchema,
|
endpoints: endpointsSchema,
|
||||||
fields: joi.array(),
|
fields: joi.array(),
|
||||||
graphQL: joi.alternatives().try(
|
graphQL: joi.alternatives().try(
|
||||||
@@ -64,7 +66,6 @@ const globalSchema = joi
|
|||||||
beforeValidate: joi.array().items(joi.func()),
|
beforeValidate: joi.array().items(joi.func()),
|
||||||
}),
|
}),
|
||||||
label: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
label: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
|
||||||
slug: joi.string().required(),
|
|
||||||
typescript: joi.object().keys({
|
typescript: joi.object().keys({
|
||||||
interface: joi.string(),
|
interface: joi.string(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { DeepRequired } from 'ts-essentials'
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
CustomPreviewButtonProps,
|
CustomPreviewButtonProps,
|
||||||
CustomPublishButtonProps,
|
CustomPublishButtonType,
|
||||||
CustomSaveButtonProps,
|
CustomSaveButtonProps,
|
||||||
CustomSaveDraftButtonProps,
|
CustomSaveDraftButtonProps,
|
||||||
} from '../../admin/components/elements/types'
|
} from '../../admin/components/elements/types'
|
||||||
@@ -17,8 +17,8 @@ import type {
|
|||||||
GeneratePreviewURL,
|
GeneratePreviewURL,
|
||||||
LivePreviewConfig,
|
LivePreviewConfig,
|
||||||
} from '../../config/types'
|
} from '../../config/types'
|
||||||
import type { PayloadRequest } from '../../express/types'
|
import type { DBIdentifierName } from '../../database/types'
|
||||||
import type { RequestContext } from '../../express/types'
|
import type { PayloadRequest, RequestContext } from '../../express/types'
|
||||||
import type { Field } from '../../fields/config/types'
|
import type { Field } from '../../fields/config/types'
|
||||||
import type { Where } from '../../types'
|
import type { Where } from '../../types'
|
||||||
import type { IncomingGlobalVersions, SanitizedGlobalVersions } from '../../versions/types'
|
import type { IncomingGlobalVersions, SanitizedGlobalVersions } from '../../versions/types'
|
||||||
@@ -86,7 +86,7 @@ export type GlobalAdminOptions = {
|
|||||||
* Replaces the "Publish" button
|
* Replaces the "Publish" button
|
||||||
* + drafts must be enabled
|
* + drafts must be enabled
|
||||||
*/
|
*/
|
||||||
PublishButton?: CustomPublishButtonProps
|
PublishButton?: CustomPublishButtonType
|
||||||
/**
|
/**
|
||||||
* Replaces the "Save" button
|
* Replaces the "Save" button
|
||||||
* + drafts must be disabled
|
* + drafts must be disabled
|
||||||
@@ -170,6 +170,10 @@ export type GlobalConfig = {
|
|||||||
admin?: GlobalAdminOptions
|
admin?: GlobalAdminOptions
|
||||||
/** Extension point to add your custom data. */
|
/** Extension point to add your custom data. */
|
||||||
custom?: Record<string, any>
|
custom?: Record<string, any>
|
||||||
|
/**
|
||||||
|
* Customize the SQL table name
|
||||||
|
*/
|
||||||
|
dbName?: DBIdentifierName
|
||||||
endpoints?: Omit<Endpoint, 'root'>[] | false
|
endpoints?: Omit<Endpoint, 'root'>[] | false
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
graphQL?:
|
graphQL?:
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import type { SanitizedCollectionConfig } from '../collections/config/types'
|
import type { SanitizedCollectionConfig } from '../collections/config/types'
|
||||||
import type { SanitizedGlobalConfig } from '../globals/config/types'
|
import type { SanitizedGlobalConfig } from '../globals/config/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is not being used and will no longer be exported in the future
|
||||||
|
* @deprecated
|
||||||
|
* @param entity
|
||||||
|
*/
|
||||||
export const getVersionsModelName = (
|
export const getVersionsModelName = (
|
||||||
entity: SanitizedCollectionConfig | SanitizedGlobalConfig,
|
entity: SanitizedCollectionConfig | SanitizedGlobalConfig,
|
||||||
): string => `_${entity.slug}_versions`
|
): string => `_${entity.slug}_versions`
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import type { AfterReadHook } from 'payload/dist/collections/config/types'
|
import type { AfterReadHook } from 'payload/dist/collections/config/types'
|
||||||
|
|
||||||
|
import { adminsOrPublished } from '../access/adminsOrPublished'
|
||||||
import type { Page, Post } from '../payload-types'
|
import type { Page, Post } from '../payload-types'
|
||||||
|
|
||||||
export const populateArchiveBlock: AfterReadHook = async ({ doc, context, req: { payload } }) => {
|
export const populateArchiveBlock: AfterReadHook = async ({ doc, context, req }) => {
|
||||||
// pre-populate the archive block if `populateBy` is `collection`
|
// pre-populate the archive block if `populateBy` is `collection`
|
||||||
// then hydrate it on your front-end
|
// then hydrate it on your front-end
|
||||||
|
const payload = req.payload
|
||||||
|
const adminOrPublishedResult = await adminsOrPublished({ req: req })
|
||||||
|
const adminOrPublishedQuery = adminOrPublishedResult
|
||||||
|
|
||||||
const layoutWithArchive = await Promise.all(
|
const layoutWithArchive = await Promise.all(
|
||||||
doc.layout.map(async block => {
|
doc.layout.map(async block => {
|
||||||
@@ -36,6 +40,7 @@ export const populateArchiveBlock: AfterReadHook = async ({ doc, context, req: {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
...(typeof adminOrPublishedQuery === 'boolean' ? {} : adminOrPublishedQuery),
|
||||||
},
|
},
|
||||||
sort: '-publishedAt',
|
sort: '-publishedAt',
|
||||||
})
|
})
|
||||||
|
|||||||
19
test/_community/blockA.ts
Normal file
19
test/_community/blockA.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Block } from 'payload/dist/fields/config/types'
|
||||||
|
|
||||||
|
import { BlockB } from './blockB'
|
||||||
|
|
||||||
|
export const BlockA: Block = {
|
||||||
|
slug: 'block-a',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'nestedBlocks',
|
||||||
|
type: 'blocks',
|
||||||
|
blocks: [BlockB],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interfaceName: 'BlockA',
|
||||||
|
}
|
||||||
15
test/_community/blockB.ts
Normal file
15
test/_community/blockB.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Block } from 'payload/dist/fields/config/types'
|
||||||
|
|
||||||
|
import { BlockC } from './blockC'
|
||||||
|
|
||||||
|
export const BlockB: Block = {
|
||||||
|
slug: 'block-b',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'nestedBlocks',
|
||||||
|
type: 'blocks',
|
||||||
|
blocks: [BlockC],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interfaceName: 'BlockB',
|
||||||
|
}
|
||||||
15
test/_community/blockC.ts
Normal file
15
test/_community/blockC.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Block } from 'payload/dist/fields/config/types'
|
||||||
|
|
||||||
|
const Recurse = require('./blockA')
|
||||||
|
|
||||||
|
export const BlockC: Block = {
|
||||||
|
slug: 'block-C',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'nestedBlocks',
|
||||||
|
type: 'blocks',
|
||||||
|
blocks: [Recurse],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interfaceName: 'BlockC',
|
||||||
|
}
|
||||||
@@ -1,24 +1,30 @@
|
|||||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||||
|
|
||||||
|
import { BlockA } from '../../blockA'
|
||||||
import { mediaSlug } from '../Media'
|
import { mediaSlug } from '../Media'
|
||||||
|
|
||||||
export const postsSlug = 'posts'
|
export const postsSlug = 'posts'
|
||||||
|
|
||||||
export const PostsCollection: CollectionConfig = {
|
export const PostsCollection: CollectionConfig = {
|
||||||
|
slug: postsSlug,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'text',
|
name: 'text',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'blocks',
|
||||||
|
type: 'blocks',
|
||||||
|
blocks: [BlockA],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'associatedMedia',
|
name: 'associatedMedia',
|
||||||
|
type: 'upload',
|
||||||
access: {
|
access: {
|
||||||
create: () => true,
|
create: () => true,
|
||||||
update: () => false,
|
update: () => false,
|
||||||
},
|
},
|
||||||
relationTo: mediaSlug,
|
relationTo: mediaSlug,
|
||||||
type: 'upload',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
slug: postsSlug,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,98 @@ export default buildConfigWithDefaults({
|
|||||||
singular: 'Relation B',
|
singular: 'Relation B',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: 'custom-schema',
|
||||||
|
dbName: 'customs',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'localizedText',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'relationship',
|
||||||
|
type: 'relationship',
|
||||||
|
hasMany: true,
|
||||||
|
relationTo: 'relation-a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'select',
|
||||||
|
type: 'select',
|
||||||
|
dbName: ({ tableName }) => `${tableName}_customSelect`,
|
||||||
|
enumName: 'selectEnum',
|
||||||
|
hasMany: true,
|
||||||
|
options: ['a', 'b', 'c'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'radio',
|
||||||
|
type: 'select',
|
||||||
|
enumName: 'radioEnum',
|
||||||
|
options: ['a', 'b', 'c'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'array',
|
||||||
|
type: 'array',
|
||||||
|
dbName: 'customArrays',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'localizedText',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'blocks',
|
||||||
|
type: 'blocks',
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
slug: 'block',
|
||||||
|
dbName: 'customBlocks',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'localizedText',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
versions: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
globals: [
|
||||||
|
{
|
||||||
|
slug: 'global',
|
||||||
|
// @ts-expect-error
|
||||||
|
dbName: 'customGlobal',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
versions: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
localization: {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'es'],
|
||||||
|
},
|
||||||
onInit: async (payload) => {
|
onInit: async (payload) => {
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'users',
|
collection: 'users',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import fs from 'fs'
|
|||||||
import { GraphQLClient } from 'graphql-request'
|
import { GraphQLClient } from 'graphql-request'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
import type { PostgresAdapter } from '../../packages/db-postgres/src/types'
|
||||||
import type { PostgresAdapter } from '../../packages/db-postgres/src/types'
|
import type { PostgresAdapter } from '../../packages/db-postgres/src/types'
|
||||||
import type { TypeWithID } from '../../packages/payload/src/collections/config/types'
|
import type { TypeWithID } from '../../packages/payload/src/collections/config/types'
|
||||||
import type { PayloadRequest } from '../../packages/payload/src/express/types'
|
import type { PayloadRequest } from '../../packages/payload/src/express/types'
|
||||||
@@ -14,6 +15,7 @@ import { initTransaction } from '../../packages/payload/src/utilities/initTransa
|
|||||||
import { devUser } from '../credentials'
|
import { devUser } from '../credentials'
|
||||||
import { initPayloadTest } from '../helpers/configHelpers'
|
import { initPayloadTest } from '../helpers/configHelpers'
|
||||||
import removeFiles from '../helpers/removeFiles'
|
import removeFiles from '../helpers/removeFiles'
|
||||||
|
import { MongooseAdapter } from '../../packages/db-mongodb/src'
|
||||||
|
|
||||||
describe('database', () => {
|
describe('database', () => {
|
||||||
let serverURL
|
let serverURL
|
||||||
@@ -140,6 +142,59 @@ describe('database', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('schema', () => {
|
||||||
|
it('should use custom dbNames', () => {
|
||||||
|
expect(payload.db).toBeDefined()
|
||||||
|
|
||||||
|
if (payload.db.name === 'mongoose') {
|
||||||
|
// @ts-expect-error
|
||||||
|
const db: MongooseAdapter = payload.db
|
||||||
|
|
||||||
|
expect(db.collections['custom-schema'].modelName).toStrictEqual('customs')
|
||||||
|
expect(db.versions['custom-schema'].modelName).toStrictEqual('_customs_versions')
|
||||||
|
expect(db.versions.global.modelName).toStrictEqual('_customGlobal_versions')
|
||||||
|
} else {
|
||||||
|
// @ts-expect-error
|
||||||
|
const db: PostgresAdapter = payload.db
|
||||||
|
|
||||||
|
// collection
|
||||||
|
expect(db.tables.customs).toBeDefined()
|
||||||
|
|
||||||
|
// collection versions
|
||||||
|
expect(db.tables._customs_v).toBeDefined()
|
||||||
|
|
||||||
|
// collection relationships
|
||||||
|
expect(db.tables.customs_rels).toBeDefined()
|
||||||
|
|
||||||
|
// collection localized
|
||||||
|
expect(db.tables.customs_locales).toBeDefined()
|
||||||
|
|
||||||
|
// global
|
||||||
|
expect(db.tables.customGlobal).toBeDefined()
|
||||||
|
expect(db.tables._customGlobal_v).toBeDefined()
|
||||||
|
|
||||||
|
// select
|
||||||
|
expect(db.tables.customs_customSelect).toBeDefined()
|
||||||
|
|
||||||
|
// array
|
||||||
|
expect(db.tables.customArrays).toBeDefined()
|
||||||
|
|
||||||
|
// array localized
|
||||||
|
expect(db.tables.customArrays_locales).toBeDefined()
|
||||||
|
|
||||||
|
// blocks
|
||||||
|
expect(db.tables.customBlocks).toBeDefined()
|
||||||
|
|
||||||
|
// localized blocks
|
||||||
|
expect(db.tables.customBlocks_locales).toBeDefined()
|
||||||
|
|
||||||
|
// enum names
|
||||||
|
expect(db.enums.selectEnum).toBeDefined()
|
||||||
|
expect(db.enums.radioEnum).toBeDefined()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('transactions', () => {
|
describe('transactions', () => {
|
||||||
describe('local api', () => {
|
describe('local api', () => {
|
||||||
it('should commit multiple operations in isolation', async () => {
|
it('should commit multiple operations in isolation', async () => {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const collectionWithName = (collectionSlug: string): CollectionConfig => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const slug = 'posts'
|
export const slug = 'posts'
|
||||||
|
export const slugWithLocalizedRel = 'postsLocalized'
|
||||||
export const relationSlug = 'relation'
|
export const relationSlug = 'relation'
|
||||||
export const defaultAccessRelSlug = 'strict-access'
|
export const defaultAccessRelSlug = 'strict-access'
|
||||||
export const chainedRelSlug = 'chained'
|
export const chainedRelSlug = 'chained'
|
||||||
@@ -46,6 +47,10 @@ export const polymorphicRelationshipsSlug = 'polymorphic-relationships'
|
|||||||
export const treeSlug = 'tree'
|
export const treeSlug = 'tree'
|
||||||
|
|
||||||
export default buildConfigWithDefaults({
|
export default buildConfigWithDefaults({
|
||||||
|
localization: {
|
||||||
|
locales: ['en', 'de'],
|
||||||
|
defaultLocale: 'en',
|
||||||
|
},
|
||||||
collections: [
|
collections: [
|
||||||
{
|
{
|
||||||
slug,
|
slug,
|
||||||
@@ -108,6 +113,23 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: slugWithLocalizedRel,
|
||||||
|
access: openAccess,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
// Relationship
|
||||||
|
{
|
||||||
|
name: 'relationField',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: relationSlug,
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
collectionWithName(relationSlug),
|
collectionWithName(relationSlug),
|
||||||
{
|
{
|
||||||
...collectionWithName(defaultAccessRelSlug),
|
...collectionWithName(defaultAccessRelSlug),
|
||||||
@@ -276,6 +298,13 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rel2 = await payload.create({
|
||||||
|
collection: relationSlug,
|
||||||
|
data: {
|
||||||
|
name: 'another name',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const filteredRelation = await payload.create({
|
const filteredRelation = await payload.create({
|
||||||
collection: relationSlug,
|
collection: relationSlug,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
Post,
|
Post,
|
||||||
Relation,
|
Relation,
|
||||||
} from './payload-types'
|
} from './payload-types'
|
||||||
|
import type { PostsLocalized } from './payload-types'
|
||||||
|
|
||||||
import payload from '../../packages/payload/src'
|
import payload from '../../packages/payload/src'
|
||||||
import { devUser } from '../credentials'
|
import { devUser } from '../credentials'
|
||||||
@@ -21,6 +22,7 @@ import config, {
|
|||||||
defaultAccessRelSlug,
|
defaultAccessRelSlug,
|
||||||
relationSlug,
|
relationSlug,
|
||||||
slug,
|
slug,
|
||||||
|
slugWithLocalizedRel,
|
||||||
treeSlug,
|
treeSlug,
|
||||||
} from './config'
|
} from './config'
|
||||||
|
|
||||||
@@ -409,6 +411,85 @@ describe('Relationships', () => {
|
|||||||
expect(doc?.relationField).toMatchObject({ id: relation.id, name: relation.name })
|
expect(doc?.relationField).toMatchObject({ id: relation.id, name: relation.name })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('with localization', () => {
|
||||||
|
let relation1: Relation
|
||||||
|
let relation2: Relation
|
||||||
|
let localizedPost1: PostsLocalized
|
||||||
|
let localizedPost2: PostsLocalized
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
relation1 = await payload.create<Relation>({
|
||||||
|
collection: relationSlug,
|
||||||
|
data: {
|
||||||
|
name: 'english',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
relation2 = await payload.create<Relation>({
|
||||||
|
collection: relationSlug,
|
||||||
|
data: {
|
||||||
|
name: 'german',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
localizedPost1 = await payload.create<'postsLocalized'>({
|
||||||
|
collection: slugWithLocalizedRel,
|
||||||
|
data: {
|
||||||
|
title: 'english',
|
||||||
|
relationField: relation1.id,
|
||||||
|
},
|
||||||
|
locale: 'en',
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.update({
|
||||||
|
id: localizedPost1.id,
|
||||||
|
collection: slugWithLocalizedRel,
|
||||||
|
locale: 'de',
|
||||||
|
data: {
|
||||||
|
relationField: relation2.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
localizedPost2 = await payload.create({
|
||||||
|
collection: slugWithLocalizedRel,
|
||||||
|
data: {
|
||||||
|
title: 'german',
|
||||||
|
relationField: relation2.id,
|
||||||
|
},
|
||||||
|
locale: 'de',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should find two docs for german locale', async () => {
|
||||||
|
const { docs } = await payload.find<PostsLocalized>({
|
||||||
|
collection: slugWithLocalizedRel,
|
||||||
|
locale: 'de',
|
||||||
|
where: {
|
||||||
|
relationField: {
|
||||||
|
equals: relation2.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const mappedIds = docs.map((doc) => doc?.id)
|
||||||
|
expect(mappedIds).toContain(localizedPost1.id)
|
||||||
|
expect(mappedIds).toContain(localizedPost2.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shouldn't find a relationship query outside of the specified locale", async () => {
|
||||||
|
const { docs } = await payload.find<PostsLocalized>({
|
||||||
|
collection: slugWithLocalizedRel,
|
||||||
|
locale: 'en',
|
||||||
|
where: {
|
||||||
|
relationField: {
|
||||||
|
equals: relation2.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(docs.map((doc) => doc?.id)).not.toContain(localizedPost2.id)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Nested Querying', () => {
|
describe('Nested Querying', () => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface Config {
|
|||||||
collections: {
|
collections: {
|
||||||
posts: Post
|
posts: Post
|
||||||
relation: Relation
|
relation: Relation
|
||||||
|
postsLocalized: PostsLocalized
|
||||||
'strict-access': StrictAccess
|
'strict-access': StrictAccess
|
||||||
'chained-relation': ChainedRelation
|
'chained-relation': ChainedRelation
|
||||||
'custom-id-relation': CustomIdRelation
|
'custom-id-relation': CustomIdRelation
|
||||||
@@ -35,6 +36,13 @@ export interface Post {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
}
|
}
|
||||||
|
export interface PostsLocalized {
|
||||||
|
id: string
|
||||||
|
title?: string | null
|
||||||
|
relationField?: (string | null) | Relation
|
||||||
|
updatedAt: string
|
||||||
|
createdAt: string
|
||||||
|
}
|
||||||
export interface Relation {
|
export interface Relation {
|
||||||
id: string
|
id: string
|
||||||
name?: string
|
name?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user