Compare commits
49 Commits
db-postgre
...
db-postgre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8c6c9338d | ||
|
|
558534aff8 | ||
|
|
29c901ba9b | ||
|
|
f3876c2a39 | ||
|
|
c3a3942969 | ||
|
|
23b135b963 | ||
|
|
e3c8105cc2 | ||
|
|
2c71aaef75 | ||
|
|
922fb9b7fa | ||
|
|
0740d5095e | ||
|
|
b392d656fe | ||
|
|
c0eef90cdc | ||
|
|
db22cbdf21 | ||
|
|
1e8a6b7899 | ||
|
|
5d934ba02d | ||
|
|
f651665f2f | ||
|
|
5d3659d48a | ||
|
|
47106d5a1a | ||
|
|
afa2b942e0 | ||
|
|
20ddd0de5b | ||
|
|
64f705c3c9 | ||
|
|
b30ea8aa6b | ||
|
|
471d2113a7 | ||
|
|
8725d41164 | ||
|
|
0bd81aa25a | ||
|
|
8c09ca9be5 | ||
|
|
90d7ee3e65 | ||
|
|
58bbd8c00f | ||
|
|
003ad065c3 | ||
|
|
70715926a8 | ||
|
|
b3a6bfacf2 | ||
|
|
e1d9accb27 | ||
|
|
f2f55a84cc | ||
|
|
eba53ba60a | ||
|
|
f73d503fec | ||
|
|
6930c4e9f2 | ||
|
|
3eb681e847 | ||
|
|
cb4638cfa1 | ||
|
|
b40e9f85a2 | ||
|
|
e5a7907a72 | ||
|
|
3f25d1ca84 | ||
|
|
d5720bea7b | ||
|
|
8ce15c8b07 | ||
|
|
9f5efef78f | ||
|
|
dfba5222f3 | ||
|
|
b99d24fcfa | ||
|
|
836ed77568 | ||
|
|
1c5d5b07c8 | ||
|
|
da5f1f2240 |
24
.github/workflows/main.yml
vendored
24
.github/workflows/main.yml
vendored
@@ -85,7 +85,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
database: [mongoose, postgres]
|
database: [mongoose, postgres, supabase]
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
@@ -118,7 +118,22 @@ jobs:
|
|||||||
postgresql password: ${{ env.POSTGRES_PASSWORD }}
|
postgresql password: ${{ env.POSTGRES_PASSWORD }}
|
||||||
if: matrix.database == 'postgres'
|
if: matrix.database == 'postgres'
|
||||||
|
|
||||||
- run: sleep 30
|
- name: Install Supabase CLI
|
||||||
|
uses: supabase/setup-cli@v1
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
if: matrix.database == 'supabase'
|
||||||
|
|
||||||
|
- name: Initialize Supabase
|
||||||
|
run: |
|
||||||
|
supabase init
|
||||||
|
supabase start
|
||||||
|
if: matrix.database == 'supabase'
|
||||||
|
|
||||||
|
- name: Wait for PostgreSQL
|
||||||
|
run: sleep 30
|
||||||
|
if: matrix.database == 'postgres'
|
||||||
|
|
||||||
- name: Configure PostgreSQL
|
- name: Configure PostgreSQL
|
||||||
run: |
|
run: |
|
||||||
psql "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" -c "CREATE ROLE runner SUPERUSER LOGIN;"
|
psql "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" -c "CREATE ROLE runner SUPERUSER LOGIN;"
|
||||||
@@ -126,6 +141,11 @@ jobs:
|
|||||||
echo "POSTGRES_URL=postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" >> $GITHUB_ENV
|
echo "POSTGRES_URL=postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" >> $GITHUB_ENV
|
||||||
if: matrix.database == 'postgres'
|
if: matrix.database == 'postgres'
|
||||||
|
|
||||||
|
- name: Configure Supabase
|
||||||
|
run: |
|
||||||
|
echo "POSTGRES_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres" >> $GITHUB_ENV
|
||||||
|
if: matrix.database == 'supabase'
|
||||||
|
|
||||||
- name: Component Tests
|
- name: Component Tests
|
||||||
run: pnpm test:components
|
run: pnpm test:components
|
||||||
|
|
||||||
|
|||||||
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,11 +1,52 @@
|
|||||||
|
## [2.10.0](https://github.com/payloadcms/payload/compare/v2.9.0...v2.10.0) (2024-02-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add more options to addFieldStatePromise so that it can be used for field flattening ([#4799](https://github.com/payloadcms/payload/issues/4799)) ([8725d41](https://github.com/payloadcms/payload/commit/8725d411645bb0270376e235669f46be2227ecc0))
|
||||||
|
* extend transactions to cover after and beforeOperation hooks ([#4960](https://github.com/payloadcms/payload/issues/4960)) ([1e8a6b7](https://github.com/payloadcms/payload/commit/1e8a6b7899f7b1e6451cc4d777602208478b483c))
|
||||||
|
* previousValue and previousSiblingDoc args added to beforeChange field hooks ([#4958](https://github.com/payloadcms/payload/issues/4958)) ([5d934ba](https://github.com/payloadcms/payload/commit/5d934ba02d07d98f781ce983228858ee5ce5c226))
|
||||||
|
* re-use existing logger instance passed to payload.init ([#3124](https://github.com/payloadcms/payload/issues/3124)) ([471d211](https://github.com/payloadcms/payload/commit/471d2113a790dc0d54b2f8ed84e6899310efd600))
|
||||||
|
* **richtext-lexical:** Blocks: generate type definitions for blocks fields ([#4529](https://github.com/payloadcms/payload/issues/4529)) ([90d7ee3](https://github.com/payloadcms/payload/commit/90d7ee3e6535d51290fc734b284ff3811dbda1f8))
|
||||||
|
* use deletion success message from server if provided ([#4966](https://github.com/payloadcms/payload/issues/4966)) ([e3c8105](https://github.com/payloadcms/payload/commit/e3c8105cc2ed6fdf8007d97cd7b5556fc71ed724))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **db-postgres:** filtering relationships with drafts enabled ([#4998](https://github.com/payloadcms/payload/issues/4998)) ([c3a3942](https://github.com/payloadcms/payload/commit/c3a39429697e9d335e9be199e7caafb82eb26219))
|
||||||
|
* **db-postgres:** handle schema changes with supabase ([#4968](https://github.com/payloadcms/payload/issues/4968)) ([5d3659d](https://github.com/payloadcms/payload/commit/5d3659d48ad8bbf5d96fbcd80434d2287cab97e0))
|
||||||
|
* **db-postgres:** indexes not created for non unique field names ([#4967](https://github.com/payloadcms/payload/issues/4967)) ([64f705c](https://github.com/payloadcms/payload/commit/64f705c3c94148972f67e8175e718015760d6430))
|
||||||
|
* **db-postgres:** indexes not creating for relationships, arrays, hasmany and blocks ([#4976](https://github.com/payloadcms/payload/issues/4976)) ([47106d5](https://github.com/payloadcms/payload/commit/47106d5a1af2ebd073fbbc6e474174c3d3835e5c))
|
||||||
|
* **db-postgres:** localized field sort count ([#4997](https://github.com/payloadcms/payload/issues/4997)) ([f3876c2](https://github.com/payloadcms/payload/commit/f3876c2a39efe19a1864213306725aadcc14f130))
|
||||||
|
* ensures docPermissions fallback to collection permissions on create ([#4969](https://github.com/payloadcms/payload/issues/4969)) ([afa2b94](https://github.com/payloadcms/payload/commit/afa2b942e0aad90c55744ae13e0ffe1cefa4585d))
|
||||||
|
* **migrations:** safely create migration file when no name passed ([#4995](https://github.com/payloadcms/payload/issues/4995)) ([0740d50](https://github.com/payloadcms/payload/commit/0740d5095ee1aef13e4e37f6b174d529f0f2d993))
|
||||||
|
* **plugin-seo:** tabbedUI with email field causes duplicate field ([#4944](https://github.com/payloadcms/payload/issues/4944)) ([db22cbd](https://github.com/payloadcms/payload/commit/db22cbdf21a39ed0604ab96c57ca4242eac82ce7))
|
||||||
|
|
||||||
|
## [2.9.0](https://github.com/payloadcms/payload/compare/v2.8.2...v2.9.0) (2024-01-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* forceAcceptWarning migration arg added to accept prompts ([#4874](https://github.com/payloadcms/payload/issues/4874)) ([eba53ba](https://github.com/payloadcms/payload/commit/eba53ba60afd7c5d37389377ed06a9b556058d49))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* afterLogin hook write conflicts ([#4904](https://github.com/payloadcms/payload/issues/4904)) ([3eb681e](https://github.com/payloadcms/payload/commit/3eb681e847e9c55eaaa69c22bea4f4e66c7eac36))
|
||||||
|
* **db-postgres:** migrate down error ([#4861](https://github.com/payloadcms/payload/issues/4861)) ([dfba522](https://github.com/payloadcms/payload/commit/dfba5222f3abf3f236dc9212a28e1aec7d7214d5))
|
||||||
|
* **db-postgres:** query unset relation ([#4862](https://github.com/payloadcms/payload/issues/4862)) ([8ce15c8](https://github.com/payloadcms/payload/commit/8ce15c8b07800397a50dcf790c263ed5b3cfad53))
|
||||||
|
* migrate down missing filter for latest batch ([#4860](https://github.com/payloadcms/payload/issues/4860)) ([b99d24f](https://github.com/payloadcms/payload/commit/b99d24fcfa698c493ea01c41621201abe18fabe3))
|
||||||
|
* **plugin-cloud-storage:** slow get file performance large collections ([#4927](https://github.com/payloadcms/payload/issues/4927)) ([f73d503](https://github.com/payloadcms/payload/commit/f73d503fecdfa5cefdc26ab9aad60b00563f881e))
|
||||||
|
* remove No Options dropdown from hasMany fields ([#4899](https://github.com/payloadcms/payload/issues/4899)) ([e5a7907](https://github.com/payloadcms/payload/commit/e5a7907a72c1371447ac2f71fce213ed22246092))
|
||||||
|
* upload input drawer does not show draft versions ([#4903](https://github.com/payloadcms/payload/issues/4903)) ([6930c4e](https://github.com/payloadcms/payload/commit/6930c4e9f2200853121391ad8f8df48ea66c40a4))
|
||||||
|
|
||||||
## [2.8.2](https://github.com/payloadcms/payload/compare/v2.8.1...v2.8.2) (2024-01-16)
|
## [2.8.2](https://github.com/payloadcms/payload/compare/v2.8.1...v2.8.2) (2024-01-16)
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **db-postgres:** support drizzle logging config ([#4809](https://github.com/payloadcms/payload/issues/4809)) ([371353f](https://github.com/payloadcms/payload/commit/371353f1535fbab4ebd9f56fc14fd10a30eec289))
|
* **db-postgres:** support drizzle logging config ([#4809](https://github.com/payloadcms/payload/issues/4809)) ([371353f](https://github.com/payloadcms/payload/commit/371353f1535fbab4ebd9f56fc14fd10a30eec289))
|
||||||
* **plugin-form-builder:** add validation for form ID when creating a submission
|
* **plugin-form-builder:** add validation for form ID when creating a submission ([#4730](https://github.com/payloadcms/payload/pull/4730))
|
||||||
* **plugin-seo:** allow field and interface overrides
|
* **plugin-seo:** add support for interfaceName and fieldOverrides ([#4695](https://github.com/payloadcms/payload/pull/4695))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
@@ -14,6 +55,7 @@
|
|||||||
* **db-postgres:** Remove duplicate keys from response ([#4747](https://github.com/payloadcms/payload/issues/4747)) ([eb9e771](https://github.com/payloadcms/payload/commit/eb9e771a9ca03636486d36654f215b73435574cb))
|
* **db-postgres:** Remove duplicate keys from response ([#4747](https://github.com/payloadcms/payload/issues/4747)) ([eb9e771](https://github.com/payloadcms/payload/commit/eb9e771a9ca03636486d36654f215b73435574cb))
|
||||||
* **db-postgres:** validateExistingBlockIsIdentical with arrays ([3b88adc](https://github.com/payloadcms/payload/commit/3b88adc7d0594af63ce190c40c9ee3905df67a31))
|
* **db-postgres:** validateExistingBlockIsIdentical with arrays ([3b88adc](https://github.com/payloadcms/payload/commit/3b88adc7d0594af63ce190c40c9ee3905df67a31))
|
||||||
* **db-postgres:** validateExistingBlockIsIdentical with other tables ([0647c87](https://github.com/payloadcms/payload/commit/0647c870f15dc1b122734b678c2abeb6f56377d4))
|
* **db-postgres:** validateExistingBlockIsIdentical with other tables ([0647c87](https://github.com/payloadcms/payload/commit/0647c870f15dc1b122734b678c2abeb6f56377d4))
|
||||||
|
* **plugin-seo:** fix missing spread operator in URL generator function ([#4723](https://github.com/payloadcms/payload/pull/4723))
|
||||||
* removes max-width from field-types class & correctly sets it on uploads ([#4829](https://github.com/payloadcms/payload/issues/4829)) ([ee5390a](https://github.com/payloadcms/payload/commit/ee5390aaca37a4154cde8392b60f091ec3e5175c))
|
* removes max-width from field-types class & correctly sets it on uploads ([#4829](https://github.com/payloadcms/payload/issues/4829)) ([ee5390a](https://github.com/payloadcms/payload/commit/ee5390aaca37a4154cde8392b60f091ec3e5175c))
|
||||||
|
|
||||||
## [2.8.1](https://github.com/payloadcms/payload/compare/v2.8.0...v2.8.1) (2024-01-12)
|
## [2.8.1](https://github.com/payloadcms/payload/compare/v2.8.0...v2.8.1) (2024-01-12)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ This field uses the `monaco-react` editor syntax highlighting.
|
|||||||
| **`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 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. |
|
||||||
| **`index`** | Build a 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. |
|
||||||
| **`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) |
|
||||||
| **`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) |
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const Page: CollectionConfig = {
|
|||||||
- [Date](/docs/fields/date) - date / time field that saves a timestamp
|
- [Date](/docs/fields/date) - date / time field that saves a timestamp
|
||||||
- [Email](/docs/fields/email) - validates the entry is a properly formatted email
|
- [Email](/docs/fields/email) - validates the entry is a properly formatted email
|
||||||
- [Group](/docs/fields/group) - nest fields within an object
|
- [Group](/docs/fields/group) - nest fields within an object
|
||||||
|
- [JSON](/docs/fields/json) - saves actual JSON in the database
|
||||||
- [Number](/docs/fields/number) - field that enforces that its value be a number
|
- [Number](/docs/fields/number) - field that enforces that its value be a number
|
||||||
- [Point](/docs/fields/point) - geometric coordinates for location data
|
- [Point](/docs/fields/point) - geometric coordinates for location data
|
||||||
- [Radio](/docs/fields/radio) - radio button group, allowing only one value to be selected
|
- [Radio](/docs/fields/radio) - radio button group, allowing only one value to be selected
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ caption="Admin panel screenshot of a Relationship field"
|
|||||||
| **`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 a 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) |
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import { CollectionBeforeOperationHook } from 'payload/types'
|
|||||||
const beforeOperationHook: CollectionBeforeOperationHook = async ({
|
const beforeOperationHook: CollectionBeforeOperationHook = async ({
|
||||||
args, // original arguments passed into the operation
|
args, // original arguments passed into the operation
|
||||||
operation, // name of the operation
|
operation, // name of the operation
|
||||||
|
req, // full express request
|
||||||
}) => {
|
}) => {
|
||||||
return args // return modified operation arguments as necessary
|
return args // return modified operation arguments as necessary
|
||||||
}
|
}
|
||||||
@@ -209,6 +210,7 @@ import { CollectionAfterOperationHook } from 'payload/types'
|
|||||||
const afterOperationHook: CollectionAfterOperationHook = async ({
|
const afterOperationHook: CollectionAfterOperationHook = async ({
|
||||||
args, // arguments passed into the operation
|
args, // arguments passed into the operation
|
||||||
operation, // name of the operation
|
operation, // name of the operation
|
||||||
|
req, // full express request
|
||||||
result, // the result of the operation, before modifications
|
result, // the result of the operation, before modifications
|
||||||
}) => {
|
}) => {
|
||||||
return result // return modified result as necessary
|
return result // return modified result as necessary
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ desc: Hooks can be added to any fields, and optionally modify the return value o
|
|||||||
keywords: hooks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
keywords: hooks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||||
---
|
---
|
||||||
|
|
||||||
Field-level hooks offer incredible potential for encapsulating your logic. They help to isolate concerns and package up functionalities to be easily reusable across your projects.
|
Field-level hooks offer incredible potential for encapsulating your logic. They help to isolate concerns and package up
|
||||||
|
functionalities to be easily reusable across your projects.
|
||||||
|
|
||||||
**Example use cases include:**
|
**Example use cases include:**
|
||||||
|
|
||||||
@@ -46,7 +47,8 @@ const ExampleField: Field = {
|
|||||||
|
|
||||||
## Arguments and return values
|
## Arguments and return values
|
||||||
|
|
||||||
All field-level hooks are formatted to accept the same arguments, although some arguments may be `undefined` based on which field hook you are utilizing.
|
All field-level hooks are formatted to accept the same arguments, although some arguments may be `undefined` based on
|
||||||
|
which field hook you are utilizing.
|
||||||
|
|
||||||
<Banner type="success">
|
<Banner type="success">
|
||||||
<strong>Tip:</strong>
|
<strong>Tip:</strong>
|
||||||
@@ -69,10 +71,10 @@ Field Hooks receive one `args` argument that contains the following properties:
|
|||||||
| **`operation`** | A string relating to which operation the field type is currently executing within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. |
|
| **`operation`** | A string relating to which operation the field type is currently executing within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. |
|
||||||
| **`originalDoc`** | The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. |
|
| **`originalDoc`** | The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. |
|
||||||
| **`previousDoc`** | The document before changes were applied, only in `afterChange` hooks. |
|
| **`previousDoc`** | The document before changes were applied, only in `afterChange` hooks. |
|
||||||
| **`previousSiblingDoc`** | The sibling data from the previous document in `afterChange` hook. |
|
| **`previousSiblingDoc`** | The sibling data of the document before changes being applied, only in `beforeChange` and `afterChange` hook. |
|
||||||
| **`req`** | The Express `request` object. It is mocked for Local API operations. |
|
| **`req`** | The Express `request` object. It is mocked for Local API operations. |
|
||||||
| **`value`** | The value of the field. |
|
| **`value`** | The value of the field. |
|
||||||
| **`previousValue`** | The previous value of the field, before changes were applied, only in `afterChange` hooks. |
|
| **`previousValue`** | The previous value of the field, before changes, only in `beforeChange` and `afterChange` hooks. |
|
||||||
| **`context`** | Context passed to this hook. More info can be found under [Context](/docs/hooks/context) |
|
| **`context`** | Context passed to this hook. More info can be found under [Context](/docs/hooks/context) |
|
||||||
| **`field`** | The field which the hook is running against. |
|
| **`field`** | The field which the hook is running against. |
|
||||||
| **`collection`** | The collection which the field belongs to. If the field belongs to a global, this will be null. |
|
| **`collection`** | The collection which the field belongs to. If the field belongs to a global, this will be null. |
|
||||||
@@ -80,7 +82,8 @@ Field Hooks receive one `args` argument that contains the following properties:
|
|||||||
|
|
||||||
#### Return value
|
#### Return value
|
||||||
|
|
||||||
All field hooks can optionally modify the return value of the field before the operation continues. Field Hooks may optionally return the value that should be used within the field.
|
All field hooks can optionally modify the return value of the field before the operation continues. Field Hooks may
|
||||||
|
optionally return the value that should be used within the field.
|
||||||
|
|
||||||
<Banner type="warning">
|
<Banner type="warning">
|
||||||
<strong>Important</strong>
|
<strong>Important</strong>
|
||||||
@@ -92,11 +95,14 @@ All field hooks can optionally modify the return value of the field before the o
|
|||||||
|
|
||||||
## Examples of Field Hooks
|
## Examples of Field Hooks
|
||||||
|
|
||||||
To better illustrate how field-level hooks can be applied, here are some specific examples. These demonstrate the flexibility and potential of field hooks in different contexts. Remember, these examples are just a starting point - the true potential of field-level hooks lies in their adaptability to a wide array of use cases.
|
To better illustrate how field-level hooks can be applied, here are some specific examples. These demonstrate the
|
||||||
|
flexibility and potential of field hooks in different contexts. Remember, these examples are just a starting point - the
|
||||||
|
true potential of field-level hooks lies in their adaptability to a wide array of use cases.
|
||||||
|
|
||||||
### beforeValidate
|
### beforeValidate
|
||||||
|
|
||||||
Runs before the `update` operation. This hook allows you to pre-process or format field data before it undergoes validation.
|
Runs before the `update` operation. This hook allows you to pre-process or format field data before it undergoes
|
||||||
|
validation.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Field } from 'payload/types'
|
import { Field } from 'payload/types'
|
||||||
@@ -113,11 +119,15 @@ const usernameField: Field = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, the `beforeValidate` hook is used to process the `username` field. The hook takes the incoming value of the field and transforms it by trimming whitespace and converting it to lowercase. This ensures that the username is stored in a consistent format in the database.
|
In this example, the `beforeValidate` hook is used to process the `username` field. The hook takes the incoming value of
|
||||||
|
the field and transforms it by trimming whitespace and converting it to lowercase. This ensures that the username is
|
||||||
|
stored in a consistent format in the database.
|
||||||
|
|
||||||
### beforeChange
|
### beforeChange
|
||||||
|
|
||||||
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the field data that will be saved to the document is valid in accordance to your field validations.
|
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage,
|
||||||
|
you can be confident that the field data that will be saved to the document is valid in accordance to your field
|
||||||
|
validations.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Field } from 'payload/types'
|
import { Field } from 'payload/types'
|
||||||
@@ -136,11 +146,14 @@ const emailField: Field = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In the `emailField`, the `beforeChange` hook checks the `operation` type. If the operation is `create`, it performs additional validation or transformation on the email field value. This allows for operation-specific logic to be applied to the field.
|
In the `emailField`, the `beforeChange` hook checks the `operation` type. If the operation is `create`, it performs
|
||||||
|
additional validation or transformation on the email field value. This allows for operation-specific logic to be applied
|
||||||
|
to the field.
|
||||||
|
|
||||||
### afterChange
|
### afterChange
|
||||||
|
|
||||||
The `afterChange` hook is executed after a field's value has been changed and saved in the database. This hook is useful for post-processing or triggering side effects based on the new value of the field.
|
The `afterChange` hook is executed after a field's value has been changed and saved in the database. This hook is useful
|
||||||
|
for post-processing or triggering side effects based on the new value of the field.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Field } from 'payload/types'
|
import { Field } from 'payload/types'
|
||||||
@@ -165,11 +178,15 @@ const membershipStatusField: Field = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, the `afterChange` hook is used with a `membershipStatusField`, which allows users to select their membership level (Standard, Premium, VIP). The hook monitors changes in the membership status. When a change occurs, it logs the update and can be used to trigger further actions, such as tracking conversion from one tier to another or notifying them about changes in their membership benefits.
|
In this example, the `afterChange` hook is used with a `membershipStatusField`, which allows users to select their
|
||||||
|
membership level (Standard, Premium, VIP). The hook monitors changes in the membership status. When a change occurs, it
|
||||||
|
logs the update and can be used to trigger further actions, such as tracking conversion from one tier to another or
|
||||||
|
notifying them about changes in their membership benefits.
|
||||||
|
|
||||||
### afterRead
|
### afterRead
|
||||||
|
|
||||||
The `afterRead` hook is invoked after a field value is read from the database. This is ideal for formatting or transforming the field data for output.
|
The `afterRead` hook is invoked after a field value is read from the database. This is ideal for formatting or
|
||||||
|
transforming the field data for output.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Field } from 'payload/types'
|
import { Field } from 'payload/types'
|
||||||
@@ -186,8 +203,9 @@ const dateField: Field = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, the `afterRead` hook for the `dateField` is used to format the date into a more readable format using `toLocaleDateString()`. This hook modifies the way the date is presented to the user, making it more user-friendly.
|
Here, the `afterRead` hook for the `dateField` is used to format the date into a more readable format
|
||||||
|
using `toLocaleDateString()`. This hook modifies the way the date is presented to the user, making it more
|
||||||
|
user-friendly.
|
||||||
|
|
||||||
## TypeScript
|
## TypeScript
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ On boot, a seed script is included to scaffold a basic database for you to use a
|
|||||||
|
|
||||||
> NOTICE: seeding the database is destructive because it drops your current database to populate a fresh one from the seed template. Only run this command if you are starting a new project or can afford to lose your current data.
|
> NOTICE: seeding the database is destructive because it drops your current database to populate a fresh one from the seed template. Only run this command if you are starting a new project or can afford to lose your current data.
|
||||||
|
|
||||||
|
### Conflicting routes
|
||||||
|
|
||||||
|
>In a monorepo when routes are bootstrapped to the same host, they can conflict with Payload's own routes if they have the same name. In our template we've named the Nextjs API routes to `next` to avoid this conflict.
|
||||||
|
>
|
||||||
|
>This can happen with any other routes conflicting with Payload such as `admin` and we recommend using different names for custom routes.
|
||||||
|
>Alternatively you can also rename Payload's own routes via the [configuration](https://payloadcms.com/docs/configuration/overview).
|
||||||
|
|
||||||
## Production
|
## Production
|
||||||
|
|
||||||
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
|
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server'
|
|
||||||
|
|
||||||
export async function GET(): Promise<NextResponse> {
|
|
||||||
return NextResponse.json({ success: true })
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server'
|
|
||||||
|
|
||||||
export async function POST(): Promise<NextResponse> {
|
|
||||||
return NextResponse.json({ success: true })
|
|
||||||
}
|
|
||||||
10
examples/custom-server/src/app/next/test-get/route.ts
Normal file
10
examples/custom-server/src/app/next/test-get/route.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Next.js API routes can conflict with Payload's own routes if they share the same path
|
||||||
|
* To avoid this you can customise the path of Payload or the API route of Nextjs as we've done here
|
||||||
|
* See readme: https://github.com/payloadcms/payload/tree/main/examples/custom-server#conflicting-routes
|
||||||
|
* */
|
||||||
|
export async function GET(): Promise<NextResponse> {
|
||||||
|
return NextResponse.json({ success: true })
|
||||||
|
}
|
||||||
10
examples/custom-server/src/app/next/test-post/route.ts
Normal file
10
examples/custom-server/src/app/next/test-post/route.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Next.js API routes can conflict with Payload's own routes if they share the same path
|
||||||
|
* To avoid this you can customise the path of Payload or the API route of Nextjs as we've done here
|
||||||
|
* See readme: https://github.com/payloadcms/payload/tree/main/examples/custom-server#conflicting-routes
|
||||||
|
* */
|
||||||
|
export async function POST(): Promise<NextResponse> {
|
||||||
|
return NextResponse.json({ success: true })
|
||||||
|
}
|
||||||
2
examples/hierarchy/.env.example
Normal file
2
examples/hierarchy/.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DATABASE_URI=mongodb://127.0.0.1/payload-template-blank
|
||||||
|
PAYLOAD_SECRET=YOUR_SECRET_HERE
|
||||||
6
examples/hierarchy/.gitignore
vendored
Normal file
6
examples/hierarchy/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
build
|
||||||
|
dist
|
||||||
|
/media
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
8
examples/hierarchy/.prettierrc.js
Normal file
8
examples/hierarchy/.prettierrc.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
printWidth: 100,
|
||||||
|
parser: 'typescript',
|
||||||
|
semi: false,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
}
|
||||||
58
examples/hierarchy/README.md
Normal file
58
examples/hierarchy/README.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Payload Hierarchy Example
|
||||||
|
|
||||||
|
This example demonstrates how to achieve a virtual hierarchy between documents in your [Payload](https://github.com/payloadcms/payload) application.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
To spin up the project locally, follow these steps:
|
||||||
|
|
||||||
|
1. First clone the repo
|
||||||
|
1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env`
|
||||||
|
1. Next `yarn && yarn dev` (or `docker-compose up`, see [Docker](#docker))
|
||||||
|
1. Now `open http://localhost:3000/admin` to access the admin panel
|
||||||
|
1. Create your first admin user using the form on the page
|
||||||
|
|
||||||
|
That's it! Changes made in `./src` will be reflected in your app.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
This example achieves parent/child relationships between your documents through the use of virtual fields. When you query a document with the `?children=true` query param, an afterRead hook is used to populate the documents within its own tree.
|
||||||
|
|
||||||
|
For more information on how virtual fields, see the [Official Virtual Fields Example](https://github.com/payloadcms/payload/tree/main/examples/virtual-fields).
|
||||||
|
|
||||||
|
### Collections
|
||||||
|
|
||||||
|
See the [Collections](https://payloadcms.com/docs/configuration/collections) docs for details on how to extend any of this functionality.
|
||||||
|
|
||||||
|
- #### Users
|
||||||
|
|
||||||
|
The `users` collection is a default payload users collection.
|
||||||
|
|
||||||
|
- #### Entities
|
||||||
|
|
||||||
|
The `entities` collection can define a parent as any other entity. It has a virtual field that will also populate children when it is called via the API using a query `children=true`. See [Virtual Fields](https://github.com/payloadcms/payload/tree/main/examples/virtual-fields) for more details on how virtual fields work.
|
||||||
|
|
||||||
|
The virtual field retrieves __all__ children which includes other entities and people.
|
||||||
|
|
||||||
|
- #### People
|
||||||
|
|
||||||
|
The `people` collection is a collection that can define an array of parent entities. It also has an allocation field. This is for demonstrating attaching data to a parent-child relationship.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To spin up this example locally, follow the [Quick Start](#quick-start).
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
|
||||||
|
|
||||||
|
1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle.
|
||||||
|
1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory.
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
The easiest way to deploy your project is to use [Payload Cloud](https://payloadcms.com/new/import), a one-click hosting solution to deploy production-ready instances of your Payload apps directly from your GitHub repo. You can also deploy your app manually, check out the [deployment documentation](https://payloadcms.com/docs/production/deployment) for full details.
|
||||||
|
|
||||||
|
## Questions
|
||||||
|
|
||||||
|
If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions).
|
||||||
6
examples/hierarchy/nodemon.json
Normal file
6
examples/hierarchy/nodemon.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nodemon.json",
|
||||||
|
"ext": "ts",
|
||||||
|
"exec": "ts-node src/server.ts -- -I",
|
||||||
|
"stdin": false
|
||||||
|
}
|
||||||
35
examples/hierarchy/package.json
Normal file
35
examples/hierarchy/package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "hierarchy",
|
||||||
|
"description": "A hierarchy example with Payload",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "dist/server.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
|
||||||
|
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
|
||||||
|
"build:server": "tsc",
|
||||||
|
"build": "yarn copyfiles && yarn build:payload && yarn build:server",
|
||||||
|
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
|
||||||
|
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
|
||||||
|
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
|
||||||
|
"generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema",
|
||||||
|
"payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@payloadcms/bundler-webpack": "^1.0.0",
|
||||||
|
"@payloadcms/db-mongodb": "^1.0.0",
|
||||||
|
"@payloadcms/plugin-cloud": "^3.0.0",
|
||||||
|
"@payloadcms/richtext-slate": "^1.0.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"payload": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.17.9",
|
||||||
|
"copyfiles": "^2.4.1",
|
||||||
|
"nodemon": "^2.0.6",
|
||||||
|
"ts-node": "^9.1.1",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
79
examples/hierarchy/src/collections/Entities.ts
Normal file
79
examples/hierarchy/src/collections/Entities.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { CollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
|
export const Entities: CollectionConfig = {
|
||||||
|
slug: 'entities',
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'name',
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
// - This field is populated by setting the query parameter 'children=true'
|
||||||
|
// - This is a virtual field used to track a child relationship
|
||||||
|
// - Only relationship information is returned by this field
|
||||||
|
// - Data beyond relationships is not stored in this field
|
||||||
|
{
|
||||||
|
name: 'children',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: ['entities', 'people'],
|
||||||
|
access: {
|
||||||
|
create: () => false,
|
||||||
|
update: () => false,
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
afterRead: [
|
||||||
|
async ({ data, req }) => {
|
||||||
|
const { id } = data
|
||||||
|
|
||||||
|
if (!req.query.children) return
|
||||||
|
|
||||||
|
const people = await req.payload.find({
|
||||||
|
req,
|
||||||
|
collection: 'people',
|
||||||
|
where: {
|
||||||
|
'parents.parent': { equals: id },
|
||||||
|
},
|
||||||
|
limit: 0,
|
||||||
|
depth: 0,
|
||||||
|
pagination: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const entities = await req.payload.find({
|
||||||
|
req,
|
||||||
|
collection: 'entities',
|
||||||
|
where: {
|
||||||
|
parent: { equals: id },
|
||||||
|
},
|
||||||
|
limit: 0,
|
||||||
|
depth: 0,
|
||||||
|
pagination: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return [
|
||||||
|
...entities.docs.map(entity => {
|
||||||
|
return {
|
||||||
|
relationTo: 'entity',
|
||||||
|
value: entity,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
...people.docs.map(person => {
|
||||||
|
return {
|
||||||
|
relationTo: 'people',
|
||||||
|
value: person,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parent',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'entities',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
32
examples/hierarchy/src/collections/People.ts
Normal file
32
examples/hierarchy/src/collections/People.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { CollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
|
export const People: CollectionConfig = {
|
||||||
|
slug: 'people',
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'name',
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parents',
|
||||||
|
type: 'array',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'parent',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'entities',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'allocation',
|
||||||
|
type: 'number',
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
15
examples/hierarchy/src/collections/Users.ts
Normal file
15
examples/hierarchy/src/collections/Users.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { CollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
|
const Users: CollectionConfig = {
|
||||||
|
slug: 'users',
|
||||||
|
auth: true,
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'email',
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
// Email added by default
|
||||||
|
// Add more fields as needed
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Users
|
||||||
30
examples/hierarchy/src/payload.config.ts
Normal file
30
examples/hierarchy/src/payload.config.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import { payloadCloud } from '@payloadcms/plugin-cloud'
|
||||||
|
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||||
|
import { webpackBundler } from '@payloadcms/bundler-webpack'
|
||||||
|
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||||
|
import { buildConfig } from 'payload/config'
|
||||||
|
|
||||||
|
import Users from './collections/Users'
|
||||||
|
import { Entities } from './collections/Entities'
|
||||||
|
import { People } from './collections/People'
|
||||||
|
|
||||||
|
export default buildConfig({
|
||||||
|
admin: {
|
||||||
|
user: Users.slug,
|
||||||
|
bundler: webpackBundler(),
|
||||||
|
},
|
||||||
|
editor: slateEditor({}),
|
||||||
|
collections: [Users, Entities, People],
|
||||||
|
typescript: {
|
||||||
|
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||||
|
},
|
||||||
|
graphQL: {
|
||||||
|
schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
|
||||||
|
},
|
||||||
|
plugins: [payloadCloud()],
|
||||||
|
db: mongooseAdapter({
|
||||||
|
url: process.env.DATABASE_URI,
|
||||||
|
}),
|
||||||
|
})
|
||||||
27
examples/hierarchy/src/server.ts
Normal file
27
examples/hierarchy/src/server.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import payload from 'payload'
|
||||||
|
|
||||||
|
require('dotenv').config()
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
// Redirect root to Admin panel
|
||||||
|
app.get('/', (_, res) => {
|
||||||
|
res.redirect('/admin')
|
||||||
|
})
|
||||||
|
|
||||||
|
const start = async () => {
|
||||||
|
// Initialize Payload
|
||||||
|
await payload.init({
|
||||||
|
secret: process.env.PAYLOAD_SECRET,
|
||||||
|
express: app,
|
||||||
|
onInit: async () => {
|
||||||
|
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add your own express routes here
|
||||||
|
|
||||||
|
app.listen(3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
||||||
22
examples/hierarchy/tsconfig.json
Normal file
22
examples/hierarchy/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"strict": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"jsx": "react",
|
||||||
|
"paths": {
|
||||||
|
"payload/generated-types": ["./src/payload-types.ts"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist", "build"],
|
||||||
|
"ts-node": {
|
||||||
|
"transpileOnly": true,
|
||||||
|
"swc": true
|
||||||
|
}
|
||||||
|
}
|
||||||
7896
examples/hierarchy/yarn.lock
Normal file
7896
examples/hierarchy/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,26 +2,27 @@ import type { AfterLoginHook } from 'payload/dist/collections/config/types'
|
|||||||
|
|
||||||
export const recordLastLoggedInTenant: AfterLoginHook = async ({ req, user }) => {
|
export const recordLastLoggedInTenant: AfterLoginHook = async ({ req, user }) => {
|
||||||
try {
|
try {
|
||||||
const relatedOrg = await req.payload.find({
|
const relatedOrg = await req.payload
|
||||||
collection: 'tenants',
|
.find({
|
||||||
where: {
|
collection: 'tenants',
|
||||||
'domains.domain': {
|
where: {
|
||||||
in: [req.headers.host],
|
'domains.domain': {
|
||||||
},
|
in: [req.headers.host],
|
||||||
},
|
},
|
||||||
depth: 0,
|
|
||||||
limit: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (relatedOrg.docs.length > 0) {
|
|
||||||
await req.payload.update({
|
|
||||||
id: user.id,
|
|
||||||
collection: 'users',
|
|
||||||
data: {
|
|
||||||
lastLoggedInTenant: relatedOrg.docs[0].id,
|
|
||||||
},
|
},
|
||||||
|
depth: 0,
|
||||||
|
limit: 1,
|
||||||
})
|
})
|
||||||
}
|
?.then(res => res.docs?.[0])
|
||||||
|
|
||||||
|
await req.payload.update({
|
||||||
|
id: user.id,
|
||||||
|
collection: 'users',
|
||||||
|
data: {
|
||||||
|
lastLoggedInTenant: relatedOrg?.id || null,
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
})
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
req.payload.logger.error(`Error recording last logged in tenant for user ${user.id}: ${err}`)
|
req.payload.logger.error(`Error recording last logged in tenant for user ${user.id}: ${err}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const isSuperOrTenantAdmin = async (args: { req: PayloadRequest }): Promi
|
|||||||
},
|
},
|
||||||
depth: 0,
|
depth: 0,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
|
req,
|
||||||
})
|
})
|
||||||
|
|
||||||
// if this tenant does not exist, deny access
|
// if this tenant does not exist, deny access
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -64,7 +64,7 @@
|
|||||||
"copyfiles": "2.4.1",
|
"copyfiles": "2.4.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"dotenv": "8.6.0",
|
"dotenv": "8.6.0",
|
||||||
"drizzle-orm": "0.28.5",
|
"drizzle-orm": "0.29.3",
|
||||||
"express": "4.18.2",
|
"express": "4.18.2",
|
||||||
"form-data": "3.0.1",
|
"form-data": "3.0.1",
|
||||||
"fs-extra": "10.1.0",
|
"fs-extra": "10.1.0",
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
"slash": "3.0.0",
|
"slash": "3.0.0",
|
||||||
"slate": "0.91.4",
|
"slate": "0.91.4",
|
||||||
"tempfile": "^3.0.0",
|
"tempfile": "^3.0.0",
|
||||||
"ts-node": "10.9.1",
|
"ts-node": "10.9.2",
|
||||||
"turbo": "^1.11.1",
|
"turbo": "^1.11.1",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
@@ -104,6 +104,16 @@
|
|||||||
"react-i18next": "11.18.6",
|
"react-i18next": "11.18.6",
|
||||||
"react-router-dom": "5.3.4"
|
"react-router-dom": "5.3.4"
|
||||||
},
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"copyfiles": "2.4.1",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
|
"dotenv": "8.6.0",
|
||||||
|
"drizzle-orm": "0.29.3",
|
||||||
|
"ts-node": "10.9.2",
|
||||||
|
"typescript": "5.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14",
|
"node": ">=14",
|
||||||
"pnpm": ">=8"
|
"pnpm": ">=8"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/db-mongodb",
|
"name": "@payloadcms/db-mongodb",
|
||||||
"version": "1.3.2",
|
"version": "1.4.0",
|
||||||
"description": "The officially supported MongoDB database adapter for Payload",
|
"description": "The officially supported MongoDB database adapter for Payload",
|
||||||
"repository": "https://github.com/payloadcms/payload",
|
"repository": "https://github.com/payloadcms/payload",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -29,15 +29,18 @@ export const connect: Connect = async function connect(this: MongooseAdapter, pa
|
|||||||
urlToConnect = process.env.PAYLOAD_TEST_MONGO_URL
|
urlToConnect = process.env.PAYLOAD_TEST_MONGO_URL
|
||||||
} else {
|
} else {
|
||||||
connectionOptions.dbName = 'payloadmemory'
|
connectionOptions.dbName = 'payloadmemory'
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server')
|
const { MongoMemoryReplSet } = require('mongodb-memory-server')
|
||||||
const getPort = require('get-port')
|
const getPort = require('get-port')
|
||||||
|
|
||||||
const port = await getPort()
|
const port = await getPort()
|
||||||
this.mongoMemoryServer = await MongoMemoryServer.create({
|
this.mongoMemoryServer = await MongoMemoryReplSet.create({
|
||||||
instance: {
|
instance: {
|
||||||
dbName: 'payloadmemory',
|
dbName: 'payloadmemory',
|
||||||
port,
|
port,
|
||||||
},
|
},
|
||||||
|
replSet: {
|
||||||
|
count: 3,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
urlToConnect = this.mongoMemoryServer.getUri()
|
urlToConnect = this.mongoMemoryServer.getUri()
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const createMigration: CreateMigration = async function createMigration({
|
|||||||
|
|
||||||
// Check for predefined migration.
|
// Check for predefined migration.
|
||||||
// Either passed in via --file or prefixed with @payloadcms/db-mongodb/
|
// Either passed in via --file or prefixed with @payloadcms/db-mongodb/
|
||||||
if (file || migrationName.startsWith('@payloadcms/db-mongodb/')) {
|
if (file || migrationName?.startsWith('@payloadcms/db-mongodb/')) {
|
||||||
if (!file) file = migrationName
|
if (!file) file = migrationName
|
||||||
|
|
||||||
const predefinedMigrationName = file.replace('@payloadcms/db-mongodb/', '')
|
const predefinedMigrationName = file.replace('@payloadcms/db-mongodb/', '')
|
||||||
@@ -59,8 +59,8 @@ export const createMigration: CreateMigration = async function createMigration({
|
|||||||
|
|
||||||
const timestamp = `${formattedDate}_${formattedTime}`
|
const timestamp = `${formattedDate}_${formattedTime}`
|
||||||
|
|
||||||
const formattedName = migrationName.replace(/\W/g, '_')
|
const formattedName = migrationName?.replace(/\W/g, '_')
|
||||||
const fileName = `${timestamp}_${formattedName}.ts`
|
const fileName = migrationName ? `${timestamp}_${formattedName}.ts` : `${timestamp}_migration.ts`
|
||||||
const filePath = `${dir}/${fileName}`
|
const filePath = `${dir}/${fileName}`
|
||||||
fs.writeFileSync(filePath, migrationFileContent)
|
fs.writeFileSync(filePath, migrationFileContent)
|
||||||
payload.logger.info({ msg: `Migration created at ${filePath}` })
|
payload.logger.info({ msg: `Migration created at ${filePath}` })
|
||||||
|
|||||||
@@ -11,25 +11,30 @@ import type { MongooseAdapter } from '.'
|
|||||||
/**
|
/**
|
||||||
* Drop the current database and run all migrate up functions
|
* Drop the current database and run all migrate up functions
|
||||||
*/
|
*/
|
||||||
export async function migrateFresh(this: MongooseAdapter): Promise<void> {
|
export async function migrateFresh(
|
||||||
|
this: MongooseAdapter,
|
||||||
|
{ forceAcceptWarning = false }: { forceAcceptWarning?: boolean },
|
||||||
|
): Promise<void> {
|
||||||
const { payload } = this
|
const { payload } = this
|
||||||
|
|
||||||
const { confirm: acceptWarning } = await prompts(
|
if (!forceAcceptWarning) {
|
||||||
{
|
const { confirm: acceptWarning } = await prompts(
|
||||||
name: 'confirm',
|
{
|
||||||
type: 'confirm',
|
name: 'confirm',
|
||||||
initial: false,
|
type: 'confirm',
|
||||||
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
initial: false,
|
||||||
},
|
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
||||||
{
|
|
||||||
onCancel: () => {
|
|
||||||
process.exit(0)
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
)
|
onCancel: () => {
|
||||||
|
process.exit(0)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if (!acceptWarning) {
|
if (!acceptWarning) {
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.logger.info({
|
payload.logger.info({
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export const sanitizeQueryValue = ({
|
|||||||
// Object equality requires the value to be the first key in the object that is being queried.
|
// Object equality requires the value to be the first key in the object that is being queried.
|
||||||
if (
|
if (
|
||||||
operator === 'equals' &&
|
operator === 'equals' &&
|
||||||
|
formattedValue &&
|
||||||
typeof formattedValue === 'object' &&
|
typeof formattedValue === 'object' &&
|
||||||
formattedValue.value &&
|
formattedValue.value &&
|
||||||
formattedValue.relationTo
|
formattedValue.relationTo
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/db-postgres",
|
"name": "@payloadcms/db-postgres",
|
||||||
"version": "0.4.0",
|
"version": "0.5.1",
|
||||||
"description": "The officially supported Postgres database adapter for Payload",
|
"description": "The officially supported Postgres database adapter for Payload",
|
||||||
"repository": "https://github.com/payloadcms/payload",
|
"repository": "https://github.com/payloadcms/payload",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libsql/client": "^0.3.1",
|
"@libsql/client": "^0.3.1",
|
||||||
"console-table-printer": "2.11.2",
|
"console-table-printer": "2.11.2",
|
||||||
"drizzle-kit": "0.20.5-608ae62",
|
"drizzle-kit": "0.20.14-1f2c838",
|
||||||
"drizzle-orm": "0.29.3",
|
"drizzle-orm": "0.29.3",
|
||||||
"pg": "8.11.3",
|
"pg": "8.11.3",
|
||||||
"prompts": "2.4.2",
|
"prompts": "2.4.2",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const getDefaultDrizzleSnapshot = (): DrizzleSnapshotJSON => ({
|
|||||||
|
|
||||||
export const createMigration: CreateMigration = async function createMigration(
|
export const createMigration: CreateMigration = async function createMigration(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
{ migrationName, payload },
|
{ forceAcceptWarning, migrationName, payload },
|
||||||
) {
|
) {
|
||||||
const dir = payload.db.migrationDir
|
const dir = payload.db.migrationDir
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
@@ -95,7 +95,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
|||||||
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
|
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
|
||||||
const sqlStatementsDown = await generateMigration(drizzleJsonAfter, drizzleJsonBefore)
|
const sqlStatementsDown = await generateMigration(drizzleJsonAfter, drizzleJsonBefore)
|
||||||
|
|
||||||
if (!sqlStatementsUp.length && !sqlStatementsDown.length) {
|
if (!sqlStatementsUp.length && !sqlStatementsDown.length && !forceAcceptWarning) {
|
||||||
const { confirm: shouldCreateBlankMigration } = await prompts(
|
const { confirm: shouldCreateBlankMigration } = await prompts(
|
||||||
{
|
{
|
||||||
name: 'confirm',
|
name: 'confirm',
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export const findMany = async function find({
|
|||||||
query: db
|
query: db
|
||||||
.select({
|
.select({
|
||||||
count: sql<number>`count
|
count: sql<number>`count
|
||||||
(*)`,
|
(DISTINCT ${adapter.tables[tableName].id})`,
|
||||||
})
|
})
|
||||||
.from(table)
|
.from(table)
|
||||||
.where(where),
|
.where(where),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export async function migrateDown(this: PostgresAdapter): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
const req = {} as PayloadRequest
|
const req = { payload } as PayloadRequest
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
|
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
|
||||||
|
|||||||
@@ -14,25 +14,30 @@ import { parseError } from './utilities/parseError'
|
|||||||
/**
|
/**
|
||||||
* Drop the current database and run all migrate up functions
|
* Drop the current database and run all migrate up functions
|
||||||
*/
|
*/
|
||||||
export async function migrateFresh(this: PostgresAdapter): Promise<void> {
|
export async function migrateFresh(
|
||||||
|
this: PostgresAdapter,
|
||||||
|
{ forceAcceptWarning = false },
|
||||||
|
): Promise<void> {
|
||||||
const { payload } = this
|
const { payload } = this
|
||||||
|
|
||||||
const { confirm: acceptWarning } = await prompts(
|
if (forceAcceptWarning === false) {
|
||||||
{
|
const { confirm: acceptWarning } = await prompts(
|
||||||
name: 'confirm',
|
{
|
||||||
type: 'confirm',
|
name: 'confirm',
|
||||||
initial: false,
|
type: 'confirm',
|
||||||
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
initial: false,
|
||||||
},
|
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
||||||
{
|
|
||||||
onCancel: () => {
|
|
||||||
process.exit(0)
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
)
|
onCancel: () => {
|
||||||
|
process.exit(0)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if (!acceptWarning) {
|
if (!acceptWarning) {
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.logger.info({
|
payload.logger.info({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { SQL } from 'drizzle-orm'
|
import type { SQL } from 'drizzle-orm'
|
||||||
import type { Field, FieldAffectingData, TabAsField } from 'payload/types'
|
import type { Field, FieldAffectingData, TabAsField } from 'payload/types'
|
||||||
|
|
||||||
import { and, eq, sql } from 'drizzle-orm'
|
import { and, eq, like, sql } from 'drizzle-orm'
|
||||||
import { alias } from 'drizzle-orm/pg-core'
|
import { alias } from 'drizzle-orm/pg-core'
|
||||||
import { APIError } from 'payload/errors'
|
import { APIError } from 'payload/errors'
|
||||||
import { fieldAffectsData, tabHasName } from 'payload/types'
|
import { fieldAffectsData, tabHasName } from 'payload/types'
|
||||||
@@ -134,7 +134,7 @@ export const getTableColumnFromPath = ({
|
|||||||
aliasTable,
|
aliasTable,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
columnPrefix: `${columnPrefix}${field.name}_`,
|
columnPrefix: `${columnPrefix}${field.name}_`,
|
||||||
constraintPath,
|
constraintPath: `${constraintPath}${field.name}.`,
|
||||||
constraints,
|
constraints,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
joinAliases,
|
joinAliases,
|
||||||
@@ -185,7 +185,7 @@ export const getTableColumnFromPath = ({
|
|||||||
aliasTable,
|
aliasTable,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
columnPrefix: `${columnPrefix}${field.name}_`,
|
columnPrefix: `${columnPrefix}${field.name}_`,
|
||||||
constraintPath,
|
constraintPath: `${constraintPath}${field.name}.`,
|
||||||
constraints,
|
constraints,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
joinAliases,
|
joinAliases,
|
||||||
@@ -317,21 +317,15 @@ export const getTableColumnFromPath = ({
|
|||||||
|
|
||||||
// Join in the relationships table
|
// Join in the relationships table
|
||||||
joinAliases.push({
|
joinAliases.push({
|
||||||
condition: eq(
|
condition: and(
|
||||||
(aliasTable || adapter.tables[rootTableName]).id,
|
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||||
aliasRelationshipTable.parent,
|
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||||
),
|
),
|
||||||
table: aliasRelationshipTable,
|
table: aliasRelationshipTable,
|
||||||
})
|
})
|
||||||
|
|
||||||
selectFields[`${relationTableName}.path`] = aliasRelationshipTable.path
|
selectFields[`${relationTableName}.path`] = aliasRelationshipTable.path
|
||||||
|
|
||||||
constraints.push({
|
|
||||||
columnName: 'path',
|
|
||||||
table: aliasRelationshipTable,
|
|
||||||
value: `${constraintPath}${field.name}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
let newAliasTable
|
let newAliasTable
|
||||||
|
|
||||||
if (typeof field.relationTo === 'string') {
|
if (typeof field.relationTo === 'string') {
|
||||||
@@ -428,7 +422,7 @@ export const getTableColumnFromPath = ({
|
|||||||
columnName: `${columnPrefix}${field.name}`,
|
columnName: `${columnPrefix}${field.name}`,
|
||||||
constraints,
|
constraints,
|
||||||
field,
|
field,
|
||||||
pathSegments: pathSegments,
|
pathSegments,
|
||||||
table: targetTable,
|
table: targetTable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,6 +207,16 @@ export async function parseParams({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (operator === 'equals' && queryValue === null) {
|
||||||
|
constraints.push(isNull(rawColumn || table[columnName]))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === 'not_equals' && queryValue === null) {
|
||||||
|
constraints.push(isNotNull(rawColumn || table[columnName]))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
constraints.push(
|
constraints.push(
|
||||||
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
|
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ type Args = {
|
|||||||
adapter: PostgresAdapter
|
adapter: PostgresAdapter
|
||||||
baseColumns?: Record<string, PgColumnBuilder>
|
baseColumns?: Record<string, PgColumnBuilder>
|
||||||
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
|
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
|
||||||
buildTexts?: boolean
|
|
||||||
buildNumbers?: boolean
|
buildNumbers?: boolean
|
||||||
buildRelationships?: boolean
|
buildRelationships?: boolean
|
||||||
|
buildTexts?: boolean
|
||||||
disableNotNull: boolean
|
disableNotNull: boolean
|
||||||
disableUnique: boolean
|
disableUnique: boolean
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
@@ -42,8 +42,8 @@ type Args = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
hasManyTextField: 'index' | boolean
|
|
||||||
hasManyNumberField: 'index' | boolean
|
hasManyNumberField: 'index' | boolean
|
||||||
|
hasManyTextField: 'index' | boolean
|
||||||
relationsToBuild: Map<string, string>
|
relationsToBuild: Map<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,9 +51,9 @@ export const buildTable = ({
|
|||||||
adapter,
|
adapter,
|
||||||
baseColumns = {},
|
baseColumns = {},
|
||||||
baseExtraConfig = {},
|
baseExtraConfig = {},
|
||||||
buildTexts,
|
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
|
buildTexts,
|
||||||
disableNotNull,
|
disableNotNull,
|
||||||
disableUnique = false,
|
disableUnique = false,
|
||||||
fields,
|
fields,
|
||||||
@@ -100,16 +100,16 @@ export const buildTable = ({
|
|||||||
columns.id = idColTypeMap[idColType]('id').primaryKey()
|
columns.id = idColTypeMap[idColType]('id').primaryKey()
|
||||||
;({
|
;({
|
||||||
hasLocalizedField,
|
hasLocalizedField,
|
||||||
hasLocalizedManyTextField,
|
|
||||||
hasLocalizedManyNumberField,
|
hasLocalizedManyNumberField,
|
||||||
|
hasLocalizedManyTextField,
|
||||||
hasLocalizedRelationshipField,
|
hasLocalizedRelationshipField,
|
||||||
hasManyTextField,
|
|
||||||
hasManyNumberField,
|
hasManyNumberField,
|
||||||
|
hasManyTextField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
buildTexts,
|
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
|
buildTexts,
|
||||||
columns,
|
columns,
|
||||||
disableNotNull,
|
disableNotNull,
|
||||||
disableUnique,
|
disableUnique,
|
||||||
@@ -196,12 +196,12 @@ export const buildTable = ({
|
|||||||
const textsTableName = `${rootTableName}_texts`
|
const textsTableName = `${rootTableName}_texts`
|
||||||
const columns: Record<string, PgColumnBuilder> = {
|
const columns: Record<string, PgColumnBuilder> = {
|
||||||
id: serial('id').primaryKey(),
|
id: serial('id').primaryKey(),
|
||||||
text: varchar('text'),
|
|
||||||
order: integer('order').notNull(),
|
order: integer('order').notNull(),
|
||||||
parent: parentIDColumnMap[idColType]('parent_id')
|
parent: parentIDColumnMap[idColType]('parent_id')
|
||||||
.references(() => table.id, { onDelete: 'cascade' })
|
.references(() => table.id, { onDelete: 'cascade' })
|
||||||
.notNull(),
|
.notNull(),
|
||||||
path: varchar('path').notNull(),
|
path: varchar('path').notNull(),
|
||||||
|
text: varchar('text'),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalizedManyTextField) {
|
if (hasLocalizedManyTextField) {
|
||||||
@@ -210,15 +210,15 @@ export const buildTable = ({
|
|||||||
|
|
||||||
textsTable = pgTable(textsTableName, columns, (cols) => {
|
textsTable = pgTable(textsTableName, columns, (cols) => {
|
||||||
const indexes: Record<string, IndexBuilder> = {
|
const indexes: Record<string, IndexBuilder> = {
|
||||||
orderParentIdx: index('order_parent_idx').on(cols.order, cols.parent),
|
orderParentIdx: index(`${textsTableName}_order_parent_idx`).on(cols.order, cols.parent),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasManyTextField === 'index') {
|
if (hasManyTextField === 'index') {
|
||||||
indexes.text_idx = index('text_idx').on(cols.text)
|
indexes.text_idx = index(`${textsTableName}_text_idx`).on(cols.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalizedManyTextField) {
|
if (hasLocalizedManyTextField) {
|
||||||
indexes.localeParent = index('locale_parent').on(cols.locale, cols.parent)
|
indexes.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexes
|
return indexes
|
||||||
@@ -254,15 +254,18 @@ export const buildTable = ({
|
|||||||
|
|
||||||
numbersTable = pgTable(numbersTableName, columns, (cols) => {
|
numbersTable = pgTable(numbersTableName, columns, (cols) => {
|
||||||
const indexes: Record<string, IndexBuilder> = {
|
const indexes: Record<string, IndexBuilder> = {
|
||||||
orderParentIdx: index('order_parent_idx').on(cols.order, cols.parent),
|
orderParentIdx: index(`${numbersTableName}_order_parent_idx`).on(cols.order, cols.parent),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasManyNumberField === 'index') {
|
if (hasManyNumberField === 'index') {
|
||||||
indexes.numberIdx = index('number_idx').on(cols.number)
|
indexes.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalizedManyNumberField) {
|
if (hasLocalizedManyNumberField) {
|
||||||
indexes.localeParent = index('locale_parent').on(cols.locale, cols.parent)
|
indexes.localeParent = index(`${numbersTableName}_locale_parent`).on(
|
||||||
|
cols.locale,
|
||||||
|
cols.parent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexes
|
return indexes
|
||||||
@@ -313,13 +316,13 @@ export const buildTable = ({
|
|||||||
|
|
||||||
relationshipsTable = pgTable(relationshipsTableName, relationshipColumns, (cols) => {
|
relationshipsTable = pgTable(relationshipsTableName, relationshipColumns, (cols) => {
|
||||||
const result: Record<string, unknown> = {
|
const result: Record<string, unknown> = {
|
||||||
order: index('order_idx').on(cols.order),
|
order: index(`${relationshipsTableName}_order_idx`).on(cols.order),
|
||||||
parentIdx: index('parent_idx').on(cols.parent),
|
parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent),
|
||||||
pathIdx: index('path_idx').on(cols.path),
|
pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalizedRelationshipField) {
|
if (hasLocalizedRelationshipField) {
|
||||||
result.localeIdx = index('locale_idx').on(cols.locale)
|
result.localeIdx = index(`${relationshipsTableName}_locale_idx`).on(cols.locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -381,5 +384,5 @@ export const buildTable = ({
|
|||||||
|
|
||||||
adapter.relations[`relations_${tableName}`] = tableRelations
|
adapter.relations[`relations_${tableName}`] = tableRelations
|
||||||
|
|
||||||
return { hasManyTextField, hasManyNumberField, relationsToBuild }
|
return { hasManyNumberField, hasManyTextField, relationsToBuild }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import type { GenericColumn } from '../types'
|
|||||||
type CreateIndexArgs = {
|
type CreateIndexArgs = {
|
||||||
columnName: string
|
columnName: string
|
||||||
name: string | string[]
|
name: string | string[]
|
||||||
|
tableName: string
|
||||||
unique?: boolean
|
unique?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createIndex = ({ name, columnName, unique }: CreateIndexArgs) => {
|
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
|
||||||
return (table: { [x: string]: GenericColumn }) => {
|
return (table: { [x: string]: GenericColumn }) => {
|
||||||
let columns
|
let columns
|
||||||
if (Array.isArray(name)) {
|
if (Array.isArray(name)) {
|
||||||
@@ -20,7 +21,8 @@ export const createIndex = ({ name, columnName, unique }: CreateIndexArgs) => {
|
|||||||
} else {
|
} else {
|
||||||
columns = [table[name]]
|
columns = [table[name]]
|
||||||
}
|
}
|
||||||
if (unique) return uniqueIndex(`${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
if (unique)
|
||||||
return index(`${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||||
|
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdent
|
|||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
adapter: PostgresAdapter
|
adapter: PostgresAdapter
|
||||||
buildTexts: boolean
|
|
||||||
buildNumbers: boolean
|
buildNumbers: boolean
|
||||||
buildRelationships: boolean
|
buildRelationships: boolean
|
||||||
|
buildTexts: boolean
|
||||||
columnPrefix?: string
|
columnPrefix?: string
|
||||||
columns: Record<string, PgColumnBuilder>
|
columns: Record<string, PgColumnBuilder>
|
||||||
disableNotNull: boolean
|
disableNotNull: boolean
|
||||||
@@ -56,18 +56,18 @@ type Args = {
|
|||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
hasLocalizedField: boolean
|
hasLocalizedField: boolean
|
||||||
hasLocalizedManyTextField: boolean
|
|
||||||
hasLocalizedManyNumberField: boolean
|
hasLocalizedManyNumberField: boolean
|
||||||
|
hasLocalizedManyTextField: boolean
|
||||||
hasLocalizedRelationshipField: boolean
|
hasLocalizedRelationshipField: boolean
|
||||||
hasManyTextField: 'index' | boolean
|
|
||||||
hasManyNumberField: 'index' | boolean
|
hasManyNumberField: 'index' | boolean
|
||||||
|
hasManyTextField: 'index' | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const traverseFields = ({
|
export const traverseFields = ({
|
||||||
adapter,
|
adapter,
|
||||||
buildTexts,
|
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
|
buildTexts,
|
||||||
columnPrefix,
|
columnPrefix,
|
||||||
columns,
|
columns,
|
||||||
disableNotNull,
|
disableNotNull,
|
||||||
@@ -122,7 +122,7 @@ export const traverseFields = ({
|
|||||||
if (
|
if (
|
||||||
(field.unique || field.index) &&
|
(field.unique || field.index) &&
|
||||||
!['array', 'blocks', 'group', 'point', 'relationship', 'upload'].includes(field.type) &&
|
!['array', 'blocks', 'group', 'point', 'relationship', 'upload'].includes(field.type) &&
|
||||||
!(field.type === 'number' && field.hasMany === true)
|
!('hasMany' in field && field.hasMany === true)
|
||||||
) {
|
) {
|
||||||
const unique = disableUnique !== true && field.unique
|
const unique = disableUnique !== true && field.unique
|
||||||
if (unique) {
|
if (unique) {
|
||||||
@@ -132,9 +132,10 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
||||||
}
|
}
|
||||||
targetIndexes[`${field.name}Idx`] = createIndex({
|
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
||||||
name: fieldName,
|
name: fieldName,
|
||||||
columnName,
|
columnName,
|
||||||
|
tableName: newTableName,
|
||||||
unique,
|
unique,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -241,17 +242,18 @@ export const traverseFields = ({
|
|||||||
string,
|
string,
|
||||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
||||||
> = {
|
> = {
|
||||||
orderIdx: (cols) => index('order_idx').on(cols.order),
|
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
|
||||||
parentIdx: (cols) => index('parent_idx').on(cols.parent),
|
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.localized) {
|
if (field.localized) {
|
||||||
baseColumns.locale = adapter.enums.enum__locales('locale').notNull()
|
baseColumns.locale = adapter.enums.enum__locales('locale').notNull()
|
||||||
baseExtraConfig.localeIdx = (cols) => index('locale_idx').on(cols.locale)
|
baseExtraConfig.localeIdx = (cols) =>
|
||||||
|
index(`${selectTableName}_locale_idx`).on(cols.locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.index) {
|
if (field.index) {
|
||||||
baseExtraConfig.value = (cols) => index('value_idx').on(cols.value)
|
baseExtraConfig.value = (cols) => index(`${selectTableName}_value_idx`).on(cols.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
@@ -304,18 +306,19 @@ export const traverseFields = ({
|
|||||||
string,
|
string,
|
||||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
||||||
> = {
|
> = {
|
||||||
_orderIdx: (cols) => index('_order_idx').on(cols._order),
|
_orderIdx: (cols) => index(`${arrayTableName}_order_idx`).on(cols._order),
|
||||||
_parentIDIdx: (cols) => index('_parent_id_idx').on(cols._parentID),
|
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.localized && adapter.payload.config.localization) {
|
if (field.localized && adapter.payload.config.localization) {
|
||||||
baseColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
baseColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||||
baseExtraConfig._localeIdx = (cols) => index('_locale_idx').on(cols._locale)
|
baseExtraConfig._localeIdx = (cols) =>
|
||||||
|
index(`${arrayTableName}_locale_idx`).on(cols._locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasManyTextField: subHasManyTextField,
|
|
||||||
hasManyNumberField: subHasManyNumberField,
|
hasManyNumberField: subHasManyNumberField,
|
||||||
|
hasManyTextField: subHasManyTextField,
|
||||||
relationsToBuild: subRelationsToBuild,
|
relationsToBuild: subRelationsToBuild,
|
||||||
} = buildTable({
|
} = buildTable({
|
||||||
adapter,
|
adapter,
|
||||||
@@ -384,19 +387,20 @@ export const traverseFields = ({
|
|||||||
string,
|
string,
|
||||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
||||||
> = {
|
> = {
|
||||||
_orderIdx: (cols) => index('order_idx').on(cols._order),
|
_orderIdx: (cols) => index(`${blockTableName}_order_idx`).on(cols._order),
|
||||||
_parentIDIdx: (cols) => index('parent_id_idx').on(cols._parentID),
|
_parentIDIdx: (cols) => index(`${blockTableName}_parent_id_idx`).on(cols._parentID),
|
||||||
_pathIdx: (cols) => index('path_idx').on(cols._path),
|
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.localized && adapter.payload.config.localization) {
|
if (field.localized && adapter.payload.config.localization) {
|
||||||
baseColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
baseColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||||
baseExtraConfig._localeIdx = (cols) => index('locale_idx').on(cols._locale)
|
baseExtraConfig._localeIdx = (cols) =>
|
||||||
|
index(`${blockTableName}_locale_idx`).on(cols._locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasManyTextField: subHasManyTextField,
|
|
||||||
hasManyNumberField: subHasManyNumberField,
|
hasManyNumberField: subHasManyNumberField,
|
||||||
|
hasManyTextField: subHasManyTextField,
|
||||||
relationsToBuild: subRelationsToBuild,
|
relationsToBuild: subRelationsToBuild,
|
||||||
} = buildTable({
|
} = buildTable({
|
||||||
adapter,
|
adapter,
|
||||||
@@ -465,16 +469,16 @@ export const traverseFields = ({
|
|||||||
if (!('name' in field)) {
|
if (!('name' in field)) {
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: groupHasLocalizedField,
|
hasLocalizedField: groupHasLocalizedField,
|
||||||
hasLocalizedManyTextField: groupHasLocalizedManyTextField,
|
|
||||||
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
||||||
|
hasLocalizedManyTextField: groupHasLocalizedManyTextField,
|
||||||
hasLocalizedRelationshipField: groupHasLocalizedRelationshipField,
|
hasLocalizedRelationshipField: groupHasLocalizedRelationshipField,
|
||||||
hasManyTextField: groupHasManyTextField,
|
|
||||||
hasManyNumberField: groupHasManyNumberField,
|
hasManyNumberField: groupHasManyNumberField,
|
||||||
|
hasManyTextField: groupHasManyTextField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
buildTexts,
|
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
|
buildTexts,
|
||||||
columnPrefix,
|
columnPrefix,
|
||||||
columns,
|
columns,
|
||||||
disableNotNull,
|
disableNotNull,
|
||||||
@@ -507,16 +511,16 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: groupHasLocalizedField,
|
hasLocalizedField: groupHasLocalizedField,
|
||||||
hasLocalizedManyTextField: groupHasLocalizedManyTextField,
|
|
||||||
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
||||||
|
hasLocalizedManyTextField: groupHasLocalizedManyTextField,
|
||||||
hasLocalizedRelationshipField: groupHasLocalizedRelationshipField,
|
hasLocalizedRelationshipField: groupHasLocalizedRelationshipField,
|
||||||
hasManyTextField: groupHasManyTextField,
|
|
||||||
hasManyNumberField: groupHasManyNumberField,
|
hasManyNumberField: groupHasManyNumberField,
|
||||||
|
hasManyTextField: groupHasManyTextField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
buildTexts,
|
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
|
buildTexts,
|
||||||
columnPrefix: `${columnName}_`,
|
columnPrefix: `${columnName}_`,
|
||||||
columns,
|
columns,
|
||||||
disableNotNull: disableNotNullFromHere,
|
disableNotNull: disableNotNullFromHere,
|
||||||
@@ -550,16 +554,16 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: tabHasLocalizedField,
|
hasLocalizedField: tabHasLocalizedField,
|
||||||
hasLocalizedManyTextField: tabHasLocalizedManyTextField,
|
|
||||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
||||||
|
hasLocalizedManyTextField: tabHasLocalizedManyTextField,
|
||||||
hasLocalizedRelationshipField: tabHasLocalizedRelationshipField,
|
hasLocalizedRelationshipField: tabHasLocalizedRelationshipField,
|
||||||
hasManyTextField: tabHasManyTextField,
|
|
||||||
hasManyNumberField: tabHasManyNumberField,
|
hasManyNumberField: tabHasManyNumberField,
|
||||||
|
hasManyTextField: tabHasManyTextField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
buildTexts,
|
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
|
buildTexts,
|
||||||
columnPrefix,
|
columnPrefix,
|
||||||
columns,
|
columns,
|
||||||
disableNotNull: disableNotNullFromHere,
|
disableNotNull: disableNotNullFromHere,
|
||||||
@@ -593,16 +597,16 @@ export const traverseFields = ({
|
|||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: rowHasLocalizedField,
|
hasLocalizedField: rowHasLocalizedField,
|
||||||
hasLocalizedManyTextField: rowHasLocalizedManyTextField,
|
|
||||||
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
|
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
|
||||||
|
hasLocalizedManyTextField: rowHasLocalizedManyTextField,
|
||||||
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
||||||
hasManyTextField: rowHasManyTextField,
|
|
||||||
hasManyNumberField: rowHasManyNumberField,
|
hasManyNumberField: rowHasManyNumberField,
|
||||||
|
hasManyTextField: rowHasManyTextField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
buildTexts,
|
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
|
buildTexts,
|
||||||
columnPrefix,
|
columnPrefix,
|
||||||
columns,
|
columns,
|
||||||
disableNotNull: disableNotNullFromHere,
|
disableNotNull: disableNotNullFromHere,
|
||||||
@@ -663,10 +667,10 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
hasLocalizedField,
|
hasLocalizedField,
|
||||||
hasLocalizedManyTextField,
|
|
||||||
hasLocalizedManyNumberField,
|
hasLocalizedManyNumberField,
|
||||||
|
hasLocalizedManyTextField,
|
||||||
hasLocalizedRelationshipField,
|
hasLocalizedRelationshipField,
|
||||||
hasManyTextField,
|
|
||||||
hasManyNumberField,
|
hasManyNumberField,
|
||||||
|
hasManyTextField,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,15 @@ import type { NodePgDatabase, NodePgQueryResultHKT } from 'drizzle-orm/node-post
|
|||||||
import type { PgColumn, PgEnum, PgTableWithColumns, PgTransaction } from 'drizzle-orm/pg-core'
|
import type { PgColumn, PgEnum, PgTableWithColumns, PgTransaction } from 'drizzle-orm/pg-core'
|
||||||
import type { Payload } from 'payload'
|
import type { Payload } from 'payload'
|
||||||
import type { BaseDatabaseAdapter } from 'payload/database'
|
import type { BaseDatabaseAdapter } from 'payload/database'
|
||||||
|
import type { PayloadRequest } from 'payload/types'
|
||||||
import type { Pool, PoolConfig } from 'pg'
|
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 = {
|
||||||
|
logger?: DrizzleConfig['logger']
|
||||||
migrationDir?: string
|
migrationDir?: string
|
||||||
pool: PoolConfig
|
pool: PoolConfig
|
||||||
logger?: DrizzleConfig['logger']
|
|
||||||
push?: boolean
|
push?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,8 +50,13 @@ export type DrizzleTransaction = PgTransaction<
|
|||||||
|
|
||||||
export type PostgresAdapter = BaseDatabaseAdapter & {
|
export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||||
drizzle: DrizzleDB
|
drizzle: DrizzleDB
|
||||||
logger: DrizzleConfig['logger']
|
|
||||||
enums: Record<string, GenericEnum>
|
enums: Record<string, GenericEnum>
|
||||||
|
/**
|
||||||
|
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
||||||
|
* Used for returning properly formed errors from unique fields
|
||||||
|
*/
|
||||||
|
fieldConstraints: Record<string, Record<string, string>>
|
||||||
|
logger: DrizzleConfig['logger']
|
||||||
pool: Pool
|
pool: Pool
|
||||||
poolOptions: Args['pool']
|
poolOptions: Args['pool']
|
||||||
push: boolean
|
push: boolean
|
||||||
@@ -64,17 +70,12 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tables: Record<string, GenericTable>
|
tables: Record<string, GenericTable>
|
||||||
/**
|
|
||||||
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
|
||||||
* Used for returning properly formed errors from unique fields
|
|
||||||
*/
|
|
||||||
fieldConstraints: Record<string, Record<string, string>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PostgresAdapterResult = (args: { payload: Payload }) => PostgresAdapter
|
export type PostgresAdapterResult = (args: { payload: Payload }) => PostgresAdapter
|
||||||
|
|
||||||
export type MigrateUpArgs = { payload: Payload }
|
export type MigrateUpArgs = { payload: Payload; req?: Partial<PayloadRequest> }
|
||||||
export type MigrateDownArgs = { payload: Payload }
|
export type MigrateDownArgs = { payload: Payload; req?: Partial<PayloadRequest> }
|
||||||
|
|
||||||
declare module 'payload' {
|
declare module 'payload' {
|
||||||
export interface DatabaseAdapter
|
export interface DatabaseAdapter
|
||||||
@@ -82,6 +83,7 @@ declare module 'payload' {
|
|||||||
BaseDatabaseAdapter {
|
BaseDatabaseAdapter {
|
||||||
drizzle: DrizzleDB
|
drizzle: DrizzleDB
|
||||||
enums: Record<string, GenericEnum>
|
enums: Record<string, GenericEnum>
|
||||||
|
fieldConstraints: Record<string, Record<string, string>>
|
||||||
pool: Pool
|
pool: Pool
|
||||||
push: boolean
|
push: boolean
|
||||||
relations: Record<string, GenericRelation>
|
relations: Record<string, GenericRelation>
|
||||||
@@ -94,6 +96,5 @@ declare module 'payload' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tables: Record<string, GenericTable>
|
tables: Record<string, GenericTable>
|
||||||
fieldConstraints: Record<string, Record<string, string>>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
"version": "2.8.2",
|
"version": "2.10.0",
|
||||||
"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",
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"@faceless-ui/scroll-info": "1.3.0",
|
"@faceless-ui/scroll-info": "1.3.0",
|
||||||
"@faceless-ui/window-info": "2.1.1",
|
"@faceless-ui/window-info": "2.1.1",
|
||||||
"@monaco-editor/react": "4.5.1",
|
"@monaco-editor/react": "4.5.1",
|
||||||
"@swc/core": "1.3.76",
|
"@swc/core": "1.3.107",
|
||||||
"@swc/register": "0.1.10",
|
"@swc/register": "0.1.10",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"body-scroll-lock": "4.0.0-beta.0",
|
"body-scroll-lock": "4.0.0-beta.0",
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
|
|||||||
if (res.status < 400) {
|
if (res.status < 400) {
|
||||||
setDeleting(false)
|
setDeleting(false)
|
||||||
toggleModal(modalSlug)
|
toggleModal(modalSlug)
|
||||||
toast.success(t('titleDeleted', { label: getTranslation(singular, i18n), title }))
|
toast.success(json.message || t('titleDeleted', { label: getTranslation(singular, i18n), title }))
|
||||||
return history.push(`${admin}/collections/${slug}`)
|
return history.push(`${admin}/collections/${slug}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const DocumentControls: React.FC<{
|
|||||||
id?: string
|
id?: string
|
||||||
isAccountView?: boolean
|
isAccountView?: boolean
|
||||||
isEditing?: boolean
|
isEditing?: boolean
|
||||||
permissions?: CollectionPermission | GlobalPermission | null
|
permissions?: CollectionPermission | GlobalPermission
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const getCustomViews = (args: {
|
|||||||
? collection?.admin?.components?.views?.Edit
|
? collection?.admin?.components?.views?.Edit
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const defaultViewKeys = Object.keys(defaultCollectionViews)
|
const defaultViewKeys = Object.keys(defaultCollectionViews())
|
||||||
|
|
||||||
customViews = Object.entries(collectionViewsConfig || {}).reduce((prev, [key, view]) => {
|
customViews = Object.entries(collectionViewsConfig || {}).reduce((prev, [key, view]) => {
|
||||||
if (defaultViewKeys.includes(key)) {
|
if (defaultViewKeys.includes(key)) {
|
||||||
@@ -38,7 +38,7 @@ export const getCustomViews = (args: {
|
|||||||
? global?.admin?.components?.views?.Edit
|
? global?.admin?.components?.views?.Edit
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const defaultViewKeys = Object.keys(defaultGlobalViews)
|
const defaultViewKeys = Object.keys(defaultGlobalViews())
|
||||||
|
|
||||||
customViews = Object.entries(globalViewsConfig || {}).reduce((prev, [key, view]) => {
|
customViews = Object.entries(globalViewsConfig || {}).reduce((prev, [key, view]) => {
|
||||||
if (defaultViewKeys.includes(key)) {
|
if (defaultViewKeys.includes(key)) {
|
||||||
|
|||||||
@@ -133,9 +133,10 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
|||||||
const moreThanOneAvailableCollection = enabledCollectionConfigs.length > 1
|
const moreThanOneAvailableCollection = enabledCollectionConfigs.length > 1
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { slug, admin: { listSearchableFields } = {} } = selectedCollectionConfig
|
const { slug, admin: { listSearchableFields } = {}, versions } = selectedCollectionConfig
|
||||||
const params: {
|
const params: {
|
||||||
cacheBust?: number
|
cacheBust?: number
|
||||||
|
draft?: string
|
||||||
limit?: number
|
limit?: number
|
||||||
page?: number
|
page?: number
|
||||||
search?: string
|
search?: string
|
||||||
@@ -172,6 +173,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
|||||||
if (sort) params.sort = sort
|
if (sort) params.sort = sort
|
||||||
if (cacheBust) params.cacheBust = cacheBust
|
if (cacheBust) params.cacheBust = cacheBust
|
||||||
if (copyOfWhere) params.where = copyOfWhere
|
if (copyOfWhere) params.where = copyOfWhere
|
||||||
|
if (versions?.drafts) params.draft = 'true'
|
||||||
|
|
||||||
setParams(params)
|
setParams(params)
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -12,42 +12,83 @@ import { fieldAffectsData, fieldHasSubFields, tabHasName } from '../../../../../
|
|||||||
import getValueWithDefault from '../../../../../fields/getDefaultValue'
|
import getValueWithDefault from '../../../../../fields/getDefaultValue'
|
||||||
import { iterateFields } from './iterateFields'
|
import { iterateFields } from './iterateFields'
|
||||||
|
|
||||||
type Args = {
|
export type AddFieldStatePromiseArgs = {
|
||||||
|
/**
|
||||||
|
* if all parents are localized, then the field is localized
|
||||||
|
*/
|
||||||
|
anyParentLocalized?: boolean
|
||||||
config: SanitizedConfig
|
config: SanitizedConfig
|
||||||
data: Data
|
data: Data
|
||||||
field: NonPresentationalField
|
field: NonPresentationalField
|
||||||
|
/**
|
||||||
|
* You can use this to filter down to only `localized` fields that require transalation (type: text, textarea, etc.). Another plugin might want to look for only `point` type fields to do some GIS function. With the filter function you can go in like a surgeon.
|
||||||
|
*/
|
||||||
|
filter?: (args: AddFieldStatePromiseArgs) => boolean
|
||||||
|
/**
|
||||||
|
* Force the value of fields like arrays or blocks to be the full value instead of the length @default false
|
||||||
|
*/
|
||||||
|
forceFullValue?: boolean
|
||||||
fullData: Data
|
fullData: Data
|
||||||
id: number | string
|
id: number | string
|
||||||
|
/**
|
||||||
|
* Whether the field schema should be included in the state
|
||||||
|
*/
|
||||||
|
includeSchema?: boolean
|
||||||
locale: string
|
locale: string
|
||||||
|
/**
|
||||||
|
* Whether to omit parent fields in the state. @default false
|
||||||
|
*/
|
||||||
|
omitParents?: boolean
|
||||||
operation: 'create' | 'update'
|
operation: 'create' | 'update'
|
||||||
passesCondition: boolean
|
passesCondition: boolean
|
||||||
path: string
|
path: string
|
||||||
preferences: {
|
preferences: {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Whether to skip checking the field's condition. @default false
|
||||||
|
*/
|
||||||
|
skipConditionChecks?: boolean
|
||||||
|
/**
|
||||||
|
* Whether to skip validating the field. @default false
|
||||||
|
*/
|
||||||
|
skipValidation?: boolean
|
||||||
state: Fields
|
state: Fields
|
||||||
t: TFunction
|
t: TFunction
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addFieldStatePromise = async ({
|
/**
|
||||||
id,
|
* Flattens the fields schema and fields data.
|
||||||
config,
|
* The output is the field path (e.g. array.0.name) mapped to a FormField object.
|
||||||
data,
|
*/
|
||||||
field,
|
export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Promise<void> => {
|
||||||
fullData,
|
const {
|
||||||
locale,
|
id,
|
||||||
operation,
|
anyParentLocalized = false,
|
||||||
passesCondition,
|
config,
|
||||||
path,
|
data,
|
||||||
preferences,
|
field,
|
||||||
state,
|
filter,
|
||||||
t,
|
forceFullValue = false,
|
||||||
user,
|
fullData,
|
||||||
}: Args): Promise<void> => {
|
includeSchema = false,
|
||||||
|
locale,
|
||||||
|
omitParents = false,
|
||||||
|
operation,
|
||||||
|
passesCondition,
|
||||||
|
path,
|
||||||
|
preferences,
|
||||||
|
skipConditionChecks = false,
|
||||||
|
skipValidation = false,
|
||||||
|
state,
|
||||||
|
t,
|
||||||
|
user,
|
||||||
|
} = args
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
const fieldState: FormField = {
|
const fieldState: FormField = {
|
||||||
condition: field.admin?.condition,
|
condition: field.admin?.condition,
|
||||||
|
fieldSchema: includeSchema ? field : undefined,
|
||||||
initialValue: undefined,
|
initialValue: undefined,
|
||||||
passesCondition,
|
passesCondition,
|
||||||
valid: true,
|
valid: true,
|
||||||
@@ -66,9 +107,9 @@ export const addFieldStatePromise = async ({
|
|||||||
data[field.name] = valueWithDefault
|
data[field.name] = valueWithDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
let validationResult: boolean | string = true
|
let validationResult: string | true = true
|
||||||
|
|
||||||
if (typeof fieldState.validate === 'function') {
|
if (typeof fieldState.validate === 'function' && !skipValidation) {
|
||||||
validationResult = await fieldState.validate(data?.[field.name], {
|
validationResult = await fieldState.validate(data?.[field.name], {
|
||||||
...field,
|
...field,
|
||||||
id,
|
id,
|
||||||
@@ -96,24 +137,36 @@ export const addFieldStatePromise = async ({
|
|||||||
const rowPath = `${path}${field.name}.${i}.`
|
const rowPath = `${path}${field.name}.${i}.`
|
||||||
row.id = row?.id || new ObjectID().toHexString()
|
row.id = row?.id || new ObjectID().toHexString()
|
||||||
|
|
||||||
state[`${rowPath}id`] = {
|
if (!omitParents && (!filter || filter(args))) {
|
||||||
initialValue: row.id,
|
state[`${rowPath}id`] = {
|
||||||
valid: true,
|
fieldSchema: includeSchema
|
||||||
value: row.id,
|
? field.fields.find((field) => 'name' in field && field.name === 'id')
|
||||||
|
: undefined,
|
||||||
|
initialValue: row.id,
|
||||||
|
valid: true,
|
||||||
|
value: row.id,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acc.promises.push(
|
acc.promises.push(
|
||||||
iterateFields({
|
iterateFields({
|
||||||
id,
|
id,
|
||||||
|
anyParentLocalized: field.localized || anyParentLocalized,
|
||||||
config,
|
config,
|
||||||
data: row,
|
data: row,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
filter,
|
||||||
|
forceFullValue,
|
||||||
fullData,
|
fullData,
|
||||||
|
includeSchema,
|
||||||
locale,
|
locale,
|
||||||
|
omitParents,
|
||||||
operation,
|
operation,
|
||||||
parentPassesCondition: passesCondition,
|
parentPassesCondition: passesCondition,
|
||||||
path: rowPath,
|
path: rowPath,
|
||||||
preferences,
|
preferences,
|
||||||
|
skipConditionChecks,
|
||||||
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
@@ -146,8 +199,8 @@ export const addFieldStatePromise = async ({
|
|||||||
fieldState.value = null
|
fieldState.value = null
|
||||||
fieldState.initialValue = null
|
fieldState.initialValue = null
|
||||||
} else {
|
} else {
|
||||||
fieldState.value = arrayValue.length
|
fieldState.value = forceFullValue ? arrayValue : arrayValue.length
|
||||||
fieldState.initialValue = arrayValue.length
|
fieldState.initialValue = forceFullValue ? arrayValue : arrayValue.length
|
||||||
|
|
||||||
if (arrayValue.length > 0) {
|
if (arrayValue.length > 0) {
|
||||||
fieldState.disableFormData = true
|
fieldState.disableFormData = true
|
||||||
@@ -157,7 +210,9 @@ export const addFieldStatePromise = async ({
|
|||||||
fieldState.rows = rowMetadata
|
fieldState.rows = rowMetadata
|
||||||
|
|
||||||
// Add field to state
|
// Add field to state
|
||||||
state[`${path}${field.name}`] = fieldState
|
if (!omitParents && (!filter || filter(args))) {
|
||||||
|
state[`${path}${field.name}`] = fieldState
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -173,36 +228,60 @@ export const addFieldStatePromise = async ({
|
|||||||
if (block) {
|
if (block) {
|
||||||
row.id = row?.id || new ObjectID().toHexString()
|
row.id = row?.id || new ObjectID().toHexString()
|
||||||
|
|
||||||
state[`${rowPath}id`] = {
|
if (!omitParents && (!filter || filter(args))) {
|
||||||
initialValue: row.id,
|
state[`${rowPath}id`] = {
|
||||||
valid: true,
|
fieldSchema: includeSchema
|
||||||
value: row.id,
|
? block.fields.find(
|
||||||
}
|
(blockField) => 'name' in blockField && blockField.name === 'id',
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
initialValue: row.id,
|
||||||
|
valid: true,
|
||||||
|
value: row.id,
|
||||||
|
}
|
||||||
|
|
||||||
state[`${rowPath}blockType`] = {
|
state[`${rowPath}blockType`] = {
|
||||||
initialValue: row.blockType,
|
fieldSchema: includeSchema
|
||||||
valid: true,
|
? block.fields.find(
|
||||||
value: row.blockType,
|
(blockField) => 'name' in blockField && blockField.name === 'blockType',
|
||||||
}
|
)
|
||||||
|
: undefined,
|
||||||
|
initialValue: row.blockType,
|
||||||
|
valid: true,
|
||||||
|
value: row.blockType,
|
||||||
|
}
|
||||||
|
|
||||||
state[`${rowPath}blockName`] = {
|
state[`${rowPath}blockName`] = {
|
||||||
initialValue: row.blockName,
|
fieldSchema: includeSchema
|
||||||
valid: true,
|
? block.fields.find(
|
||||||
value: row.blockName,
|
(blockField) => 'name' in blockField && blockField.name === 'blockName',
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
initialValue: row.blockName,
|
||||||
|
valid: true,
|
||||||
|
value: row.blockName,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acc.promises.push(
|
acc.promises.push(
|
||||||
iterateFields({
|
iterateFields({
|
||||||
id,
|
id,
|
||||||
|
anyParentLocalized: field.localized || anyParentLocalized,
|
||||||
config,
|
config,
|
||||||
data: row,
|
data: row,
|
||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
|
filter,
|
||||||
|
forceFullValue,
|
||||||
fullData,
|
fullData,
|
||||||
|
includeSchema,
|
||||||
locale,
|
locale,
|
||||||
|
omitParents,
|
||||||
operation,
|
operation,
|
||||||
parentPassesCondition: passesCondition,
|
parentPassesCondition: passesCondition,
|
||||||
path: rowPath,
|
path: rowPath,
|
||||||
preferences,
|
preferences,
|
||||||
|
skipConditionChecks,
|
||||||
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
@@ -237,8 +316,8 @@ export const addFieldStatePromise = async ({
|
|||||||
fieldState.value = null
|
fieldState.value = null
|
||||||
fieldState.initialValue = null
|
fieldState.initialValue = null
|
||||||
} else {
|
} else {
|
||||||
fieldState.value = blocksValue.length
|
fieldState.value = forceFullValue ? blocksValue : blocksValue.length
|
||||||
fieldState.initialValue = blocksValue.length
|
fieldState.initialValue = forceFullValue ? blocksValue : blocksValue.length
|
||||||
|
|
||||||
if (blocksValue.length > 0) {
|
if (blocksValue.length > 0) {
|
||||||
fieldState.disableFormData = true
|
fieldState.disableFormData = true
|
||||||
@@ -248,7 +327,9 @@ export const addFieldStatePromise = async ({
|
|||||||
fieldState.rows = rowMetadata
|
fieldState.rows = rowMetadata
|
||||||
|
|
||||||
// Add field to state
|
// Add field to state
|
||||||
state[`${path}${field.name}`] = fieldState
|
if (!omitParents && (!filter || filter(args))) {
|
||||||
|
state[`${path}${field.name}`] = fieldState
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -256,15 +337,22 @@ export const addFieldStatePromise = async ({
|
|||||||
case 'group': {
|
case 'group': {
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
id,
|
id,
|
||||||
|
anyParentLocalized: field.localized || anyParentLocalized,
|
||||||
config,
|
config,
|
||||||
data: data?.[field.name] || {},
|
data: data?.[field.name] || {},
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
filter,
|
||||||
|
forceFullValue,
|
||||||
fullData,
|
fullData,
|
||||||
|
includeSchema,
|
||||||
locale,
|
locale,
|
||||||
|
omitParents,
|
||||||
operation,
|
operation,
|
||||||
parentPassesCondition: passesCondition,
|
parentPassesCondition: passesCondition,
|
||||||
path: `${path}${field.name}.`,
|
path: `${path}${field.name}.`,
|
||||||
preferences,
|
preferences,
|
||||||
|
skipConditionChecks,
|
||||||
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
@@ -324,7 +412,9 @@ export const addFieldStatePromise = async ({
|
|||||||
fieldState.initialValue = relationshipValue
|
fieldState.initialValue = relationshipValue
|
||||||
}
|
}
|
||||||
|
|
||||||
state[`${path}${field.name}`] = fieldState
|
if (!filter || filter(args)) {
|
||||||
|
state[`${path}${field.name}`] = fieldState
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -337,7 +427,9 @@ export const addFieldStatePromise = async ({
|
|||||||
fieldState.value = relationshipValue
|
fieldState.value = relationshipValue
|
||||||
fieldState.initialValue = relationshipValue
|
fieldState.initialValue = relationshipValue
|
||||||
|
|
||||||
state[`${path}${field.name}`] = fieldState
|
if (!filter || filter(args)) {
|
||||||
|
state[`${path}${field.name}`] = fieldState
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -347,7 +439,9 @@ export const addFieldStatePromise = async ({
|
|||||||
fieldState.initialValue = valueWithDefault
|
fieldState.initialValue = valueWithDefault
|
||||||
|
|
||||||
// Add field to state
|
// Add field to state
|
||||||
state[`${path}${field.name}`] = fieldState
|
if (!filter || filter(args)) {
|
||||||
|
state[`${path}${field.name}`] = fieldState
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -356,15 +450,22 @@ export const addFieldStatePromise = async ({
|
|||||||
// Handle field types that do not use names (row, etc)
|
// Handle field types that do not use names (row, etc)
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
id,
|
id,
|
||||||
|
anyParentLocalized: field.localized || anyParentLocalized,
|
||||||
config,
|
config,
|
||||||
data,
|
data,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
filter,
|
||||||
|
forceFullValue,
|
||||||
fullData,
|
fullData,
|
||||||
|
includeSchema,
|
||||||
locale,
|
locale,
|
||||||
|
omitParents,
|
||||||
operation,
|
operation,
|
||||||
parentPassesCondition: passesCondition,
|
parentPassesCondition: passesCondition,
|
||||||
path,
|
path,
|
||||||
preferences,
|
preferences,
|
||||||
|
skipConditionChecks,
|
||||||
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
@@ -373,15 +474,22 @@ export const addFieldStatePromise = async ({
|
|||||||
const promises = field.tabs.map((tab) =>
|
const promises = field.tabs.map((tab) =>
|
||||||
iterateFields({
|
iterateFields({
|
||||||
id,
|
id,
|
||||||
|
anyParentLocalized: tab.localized || anyParentLocalized,
|
||||||
config,
|
config,
|
||||||
data: tabHasName(tab) ? data?.[tab.name] : data,
|
data: tabHasName(tab) ? data?.[tab.name] : data,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
|
filter,
|
||||||
|
forceFullValue,
|
||||||
fullData,
|
fullData,
|
||||||
|
includeSchema,
|
||||||
locale,
|
locale,
|
||||||
|
omitParents,
|
||||||
operation,
|
operation,
|
||||||
parentPassesCondition: passesCondition,
|
parentPassesCondition: passesCondition,
|
||||||
path: tabHasName(tab) ? `${path}${tab.name}.` : path,
|
path: tabHasName(tab) ? `${path}${tab.name}.` : path,
|
||||||
preferences,
|
preferences,
|
||||||
|
skipConditionChecks,
|
||||||
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -4,65 +4,123 @@ import type { User } from '../../../../../auth'
|
|||||||
import type { SanitizedConfig } from '../../../../../config/types'
|
import type { SanitizedConfig } from '../../../../../config/types'
|
||||||
import type { Field as FieldSchema } from '../../../../../fields/config/types'
|
import type { Field as FieldSchema } from '../../../../../fields/config/types'
|
||||||
import type { Data, Fields } from '../types'
|
import type { Data, Fields } from '../types'
|
||||||
|
import type { AddFieldStatePromiseArgs } from './addFieldStatePromise'
|
||||||
|
|
||||||
import { fieldIsPresentationalOnly } from '../../../../../fields/config/types'
|
import { fieldIsPresentationalOnly } from '../../../../../fields/config/types'
|
||||||
import { addFieldStatePromise } from './addFieldStatePromise'
|
import { addFieldStatePromise } from './addFieldStatePromise'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
config: SanitizedConfig
|
/**
|
||||||
|
* if any parents is localized, then the field is localized. @default false
|
||||||
|
*/
|
||||||
|
anyParentLocalized?: boolean
|
||||||
|
/**
|
||||||
|
* config is only needed for validation
|
||||||
|
*/
|
||||||
|
config?: SanitizedConfig
|
||||||
data: Data
|
data: Data
|
||||||
fields: FieldSchema[]
|
fields: FieldSchema[]
|
||||||
|
filter?: (args: AddFieldStatePromiseArgs) => boolean
|
||||||
|
/**
|
||||||
|
* Force the value of fields like arrays or blocks to be the full value instead of the length @default false
|
||||||
|
*/
|
||||||
|
forceFullValue?: boolean
|
||||||
fullData: Data
|
fullData: Data
|
||||||
id: number | string
|
id?: number | string
|
||||||
|
/**
|
||||||
|
* Whether the field schema should be included in the state. @default false
|
||||||
|
*/
|
||||||
|
includeSchema?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* operation is only needed for checking field conditions
|
||||||
|
*/
|
||||||
locale: string
|
locale: string
|
||||||
|
/**
|
||||||
|
* Whether to omit parent fields in the state. @default false
|
||||||
|
*/
|
||||||
|
omitParents?: boolean
|
||||||
|
/**
|
||||||
|
* operation is only needed for validation
|
||||||
|
*/
|
||||||
operation: 'create' | 'update'
|
operation: 'create' | 'update'
|
||||||
parentPassesCondition: boolean
|
parentPassesCondition?: boolean
|
||||||
path: string
|
/**
|
||||||
preferences: {
|
* The initial path of the field. @default ''
|
||||||
|
*/
|
||||||
|
path?: string
|
||||||
|
preferences?: {
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
state: Fields
|
/**
|
||||||
|
* Whether to skip checking the field's condition. @default false
|
||||||
|
*/
|
||||||
|
skipConditionChecks?: boolean
|
||||||
|
/**
|
||||||
|
* Whether to skip validating the field. @default false
|
||||||
|
*/
|
||||||
|
skipValidation?: boolean
|
||||||
|
state?: Fields
|
||||||
t: TFunction
|
t: TFunction
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens the fields schema and fields data
|
||||||
|
*/
|
||||||
export const iterateFields = async ({
|
export const iterateFields = async ({
|
||||||
id,
|
id,
|
||||||
|
anyParentLocalized = false,
|
||||||
config,
|
config,
|
||||||
data,
|
data,
|
||||||
fields,
|
fields,
|
||||||
|
filter,
|
||||||
|
forceFullValue = false,
|
||||||
fullData,
|
fullData,
|
||||||
|
includeSchema = false,
|
||||||
locale,
|
locale,
|
||||||
|
omitParents = false,
|
||||||
operation,
|
operation,
|
||||||
parentPassesCondition,
|
parentPassesCondition = true,
|
||||||
path = '',
|
path = '',
|
||||||
preferences,
|
preferences,
|
||||||
state,
|
skipConditionChecks = false,
|
||||||
|
skipValidation = false,
|
||||||
|
state = {},
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
}: Args): Promise<void> => {
|
}: Args): Promise<void> => {
|
||||||
const promises = []
|
const promises = []
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
const initialData = data
|
|
||||||
if (!fieldIsPresentationalOnly(field) && !field?.admin?.disabled) {
|
if (!fieldIsPresentationalOnly(field) && !field?.admin?.disabled) {
|
||||||
const passesCondition = Boolean(
|
let passesCondition = true
|
||||||
(field?.admin?.condition
|
if (!skipConditionChecks) {
|
||||||
? Boolean(field.admin.condition(fullData || {}, initialData || {}, { user }))
|
passesCondition = Boolean(
|
||||||
: true) && parentPassesCondition,
|
(field?.admin?.condition
|
||||||
)
|
? Boolean(field.admin.condition(fullData || {}, data || {}, { user }))
|
||||||
|
: true) && parentPassesCondition,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
addFieldStatePromise({
|
addFieldStatePromise({
|
||||||
id,
|
id,
|
||||||
|
anyParentLocalized,
|
||||||
config,
|
config,
|
||||||
data,
|
data,
|
||||||
field,
|
field,
|
||||||
|
filter,
|
||||||
|
forceFullValue,
|
||||||
fullData,
|
fullData,
|
||||||
|
includeSchema,
|
||||||
locale,
|
locale,
|
||||||
|
omitParents,
|
||||||
operation,
|
operation,
|
||||||
passesCondition,
|
passesCondition,
|
||||||
path,
|
path,
|
||||||
preferences,
|
preferences,
|
||||||
|
skipConditionChecks,
|
||||||
|
skipValidation,
|
||||||
state,
|
state,
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -2,11 +2,22 @@ import { unflatten as flatleyUnflatten } from 'flatley'
|
|||||||
|
|
||||||
import type { Data, Fields } from './types'
|
import type { Data, Fields } from './types'
|
||||||
|
|
||||||
const reduceFieldsToValues = (fields: Fields, unflatten?: boolean): Data => {
|
/**
|
||||||
|
* Reduce flattened form fields (Fields) to just map to the respective values instead of the full FormField object
|
||||||
|
*
|
||||||
|
* @param unflatten This also unflattens the data if `unflatten` is true. The unflattened data should match the original data structure
|
||||||
|
* @param ignoreDisableFormData - if true, will include fields that have `disableFormData` set to true, for example, blocks or arrays fields.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const reduceFieldsToValues = (
|
||||||
|
fields: Fields,
|
||||||
|
unflatten?: boolean,
|
||||||
|
ignoreDisableFormData?: boolean,
|
||||||
|
): Data => {
|
||||||
const data = {}
|
const data = {}
|
||||||
|
|
||||||
Object.keys(fields).forEach((key) => {
|
Object.keys(fields).forEach((key) => {
|
||||||
if (!fields[key].disableFormData) {
|
if (ignoreDisableFormData === true || !fields[key].disableFormData) {
|
||||||
data[key] = fields[key].value
|
data[key] = fields[key].value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type FormField = {
|
|||||||
condition?: Condition
|
condition?: Condition
|
||||||
disableFormData?: boolean
|
disableFormData?: boolean
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
|
fieldSchema?: FieldConfig
|
||||||
initialValue: unknown
|
initialValue: unknown
|
||||||
passesCondition?: boolean
|
passesCondition?: boolean
|
||||||
rows?: Row[]
|
rows?: Row[]
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ const NumberField: React.FC<Props> = (props) => {
|
|||||||
if (isOverHasMany) {
|
if (isOverHasMany) {
|
||||||
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
|
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
|
||||||
}
|
}
|
||||||
return t('general:noOptions')
|
return null
|
||||||
}}
|
}}
|
||||||
numberOnly
|
numberOnly
|
||||||
onChange={handleHasManyChange}
|
onChange={handleHasManyChange}
|
||||||
@@ -170,7 +170,7 @@ const NumberField: React.FC<Props> = (props) => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onWheel={(e) => {
|
onWheel={(e) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
e.target.blur()
|
e.target.blur()
|
||||||
}}
|
}}
|
||||||
placeholder={getTranslation(placeholder, i18n)}
|
placeholder={getTranslation(placeholder, i18n)}
|
||||||
|
|||||||
@@ -29,9 +29,14 @@ type RichTextAdapterBase<
|
|||||||
}) => Promise<void> | null
|
}) => Promise<void> | null
|
||||||
outputSchema?: ({
|
outputSchema?: ({
|
||||||
field,
|
field,
|
||||||
|
interfaceNameDefinitions,
|
||||||
isRequired,
|
isRequired,
|
||||||
}: {
|
}: {
|
||||||
field: RichTextField<Value, AdapterProps, ExtraFieldProperties>
|
field: RichTextField<Value, AdapterProps, ExtraFieldProperties>
|
||||||
|
/**
|
||||||
|
* Allows you to define new top-level interfaces that can be re-used in the output schema.
|
||||||
|
*/
|
||||||
|
interfaceNameDefinitions: Map<string, JSONSchema4>
|
||||||
isRequired: boolean
|
isRequired: boolean
|
||||||
}) => JSONSchema4
|
}) => JSONSchema4
|
||||||
populationPromise?: (data: {
|
populationPromise?: (data: {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
if (isOverHasMany) {
|
if (isOverHasMany) {
|
||||||
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
|
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
|
||||||
}
|
}
|
||||||
return t('general:noOptions')
|
return null
|
||||||
}}
|
}}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={[]}
|
options={[]}
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ const UploadInput: React.FC<UploadInputProps> = (props) => {
|
|||||||
fieldBaseClass,
|
fieldBaseClass,
|
||||||
baseClass,
|
baseClass,
|
||||||
className,
|
className,
|
||||||
|
`field-${path.replace(/\./g, '__')}`,
|
||||||
showError && 'error',
|
showError && 'error',
|
||||||
readOnly && 'read-only',
|
readOnly && 'read-only',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
|||||||
const [publishedDoc, setPublishedDoc] = useState<TypeWithID & TypeWithTimestamps>(null)
|
const [publishedDoc, setPublishedDoc] = useState<TypeWithID & TypeWithTimestamps>(null)
|
||||||
const [versions, setVersions] = useState<PaginatedDocs<Version>>(null)
|
const [versions, setVersions] = useState<PaginatedDocs<Version>>(null)
|
||||||
const [unpublishedVersions, setUnpublishedVersions] = useState<PaginatedDocs<Version>>(null)
|
const [unpublishedVersions, setUnpublishedVersions] = useState<PaginatedDocs<Version>>(null)
|
||||||
const [docPermissions, setDocPermissions] = useState<DocumentPermissions>(null)
|
|
||||||
|
|
||||||
const baseURL = `${serverURL}${api}`
|
const baseURL = `${serverURL}${api}`
|
||||||
let slug: string
|
let slug: string
|
||||||
@@ -62,6 +61,10 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [docPermissions, setDocPermissions] = useState<DocumentPermissions>(
|
||||||
|
permissions[pluralType][slug],
|
||||||
|
)
|
||||||
|
|
||||||
const getVersions = useCallback(async () => {
|
const getVersions = useCallback(async () => {
|
||||||
let versionFetchURL
|
let versionFetchURL
|
||||||
let publishedFetchURL
|
let publishedFetchURL
|
||||||
@@ -215,14 +218,14 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
|||||||
'Accept-Language': i18n.language,
|
'Accept-Language': i18n.language,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const json = await res.json()
|
try {
|
||||||
setDocPermissions(json)
|
const json = await res.json()
|
||||||
} else {
|
setDocPermissions(json)
|
||||||
// fallback to permissions from the entity type
|
} catch (e) {
|
||||||
// (i.e. create has no id)
|
console.error('Unable to fetch document permissions', e)
|
||||||
setDocPermissions(permissions[pluralType][slug])
|
}
|
||||||
}
|
}
|
||||||
}, [serverURL, api, pluralType, slug, id, permissions, i18n.language, code])
|
}, [serverURL, api, pluralType, slug, id, i18n.language, code])
|
||||||
|
|
||||||
const getDocPreferences = useCallback(async () => {
|
const getDocPreferences = useCallback(async () => {
|
||||||
return getPreference<DocumentPreferences>(preferencesKey)
|
return getPreference<DocumentPreferences>(preferencesKey)
|
||||||
@@ -262,6 +265,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
|||||||
|
|
||||||
const value: ContextType = {
|
const value: ContextType = {
|
||||||
id,
|
id,
|
||||||
|
slug,
|
||||||
collection,
|
collection,
|
||||||
docPermissions,
|
docPermissions,
|
||||||
getDocPermissions,
|
getDocPermissions,
|
||||||
@@ -271,7 +275,6 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
|||||||
preferencesKey,
|
preferencesKey,
|
||||||
publishedDoc,
|
publishedDoc,
|
||||||
setDocFieldPreferences,
|
setDocFieldPreferences,
|
||||||
slug,
|
|
||||||
unpublishedVersions,
|
unpublishedVersions,
|
||||||
versions,
|
versions,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { TypeWithVersion } from '../../../../versions/types'
|
|||||||
|
|
||||||
export type Version = TypeWithVersion<any>
|
export type Version = TypeWithVersion<any>
|
||||||
|
|
||||||
export type DocumentPermissions = CollectionPermission | GlobalPermission | null
|
export type DocumentPermissions = CollectionPermission | GlobalPermission
|
||||||
|
|
||||||
export type ContextType = {
|
export type ContextType = {
|
||||||
collection?: SanitizedCollectionConfig
|
collection?: SanitizedCollectionConfig
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ export type globalViewType =
|
|||||||
| 'Version'
|
| 'Version'
|
||||||
| 'Versions'
|
| 'Versions'
|
||||||
|
|
||||||
export const defaultGlobalViews: {
|
export const defaultGlobalViews = (): {
|
||||||
[key in globalViewType]: React.ComponentType<any>
|
[key in globalViewType]: React.ComponentType<any>
|
||||||
} = {
|
} => ({
|
||||||
API,
|
API,
|
||||||
Default: DefaultGlobalEdit,
|
Default: DefaultGlobalEdit,
|
||||||
LivePreview: LivePreviewView,
|
LivePreview: LivePreviewView,
|
||||||
@@ -27,7 +27,7 @@ export const defaultGlobalViews: {
|
|||||||
Relationships: null,
|
Relationships: null,
|
||||||
Version: VersionView,
|
Version: VersionView,
|
||||||
Versions: VersionsView,
|
Versions: VersionsView,
|
||||||
}
|
})
|
||||||
|
|
||||||
export const CustomGlobalComponent = (
|
export const CustomGlobalComponent = (
|
||||||
args: GlobalEditViewProps & {
|
args: GlobalEditViewProps & {
|
||||||
@@ -43,18 +43,14 @@ export const CustomGlobalComponent = (
|
|||||||
// For example, the Edit view:
|
// For example, the Edit view:
|
||||||
// 1. Edit?.Default
|
// 1. Edit?.Default
|
||||||
// 2. Edit?.Default?.Component
|
// 2. Edit?.Default?.Component
|
||||||
// TODO: Remove the `@ts-ignore` when a Typescript wizard arrives
|
|
||||||
// For some reason `Component` does not exist on type `Edit[view]` no matter how narrow the type is
|
|
||||||
const Component =
|
const Component =
|
||||||
typeof Edit === 'object' && typeof Edit[view] === 'function'
|
typeof Edit === 'object' && typeof Edit[view] === 'function'
|
||||||
? Edit[view]
|
? Edit[view]
|
||||||
: typeof Edit === 'object' &&
|
: typeof Edit === 'object' &&
|
||||||
typeof Edit?.[view] === 'object' &&
|
typeof Edit?.[view] === 'object' &&
|
||||||
// @ts-ignore
|
|
||||||
typeof Edit[view].Component === 'function'
|
typeof Edit[view].Component === 'function'
|
||||||
? // @ts-ignore
|
? Edit[view].Component
|
||||||
Edit[view].Component
|
: defaultGlobalViews()[view]
|
||||||
: defaultGlobalViews[view]
|
|
||||||
|
|
||||||
if (Component) {
|
if (Component) {
|
||||||
return <Component {...args} />
|
return <Component {...args} />
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ export type collectionViewType =
|
|||||||
| 'Version'
|
| 'Version'
|
||||||
| 'Versions'
|
| 'Versions'
|
||||||
|
|
||||||
export const defaultCollectionViews: {
|
export const defaultCollectionViews = (): {
|
||||||
[key in collectionViewType]: React.ComponentType<any>
|
[key in collectionViewType]: React.ComponentType<any>
|
||||||
} = {
|
} => ({
|
||||||
API,
|
API,
|
||||||
Default: DefaultCollectionEdit,
|
Default: DefaultCollectionEdit,
|
||||||
LivePreview: LivePreviewView,
|
LivePreview: LivePreviewView,
|
||||||
@@ -27,7 +27,7 @@ export const defaultCollectionViews: {
|
|||||||
Relationships: null,
|
Relationships: null,
|
||||||
Version: VersionView,
|
Version: VersionView,
|
||||||
Versions: VersionsView,
|
Versions: VersionsView,
|
||||||
}
|
})
|
||||||
|
|
||||||
export const CustomCollectionComponent = (
|
export const CustomCollectionComponent = (
|
||||||
args: CollectionEditViewProps & {
|
args: CollectionEditViewProps & {
|
||||||
@@ -43,18 +43,15 @@ export const CustomCollectionComponent = (
|
|||||||
// For example, the Edit view:
|
// For example, the Edit view:
|
||||||
// 1. Edit?.Default
|
// 1. Edit?.Default
|
||||||
// 2. Edit?.Default?.Component
|
// 2. Edit?.Default?.Component
|
||||||
// TODO: Remove the `@ts-ignore` when a Typescript wizard arrives
|
|
||||||
// For some reason `Component` does not exist on type `Edit[view]` no matter how narrow the type is
|
|
||||||
const Component =
|
const Component =
|
||||||
typeof Edit === 'object' && typeof Edit[view] === 'function'
|
typeof Edit === 'object' && typeof Edit[view] === 'function'
|
||||||
? Edit[view]
|
? Edit[view]
|
||||||
: typeof Edit === 'object' &&
|
: typeof Edit === 'object' &&
|
||||||
typeof Edit?.[view] === 'object' &&
|
typeof Edit?.[view] === 'object' &&
|
||||||
// @ts-ignore
|
|
||||||
typeof Edit[view].Component === 'function'
|
typeof Edit[view].Component === 'function'
|
||||||
? // @ts-ignore
|
? Edit[view].Component
|
||||||
Edit[view].Component
|
: defaultCollectionViews()[view]
|
||||||
: defaultCollectionViews[view]
|
|
||||||
|
|
||||||
if (Component) {
|
if (Component) {
|
||||||
return <Component {...args} />
|
return <Component {...args} />
|
||||||
|
|||||||
@@ -29,37 +29,38 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
|
|||||||
|
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection?.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'forgotPassword',
|
|
||||||
})) || args
|
|
||||||
}, Promise.resolve())
|
|
||||||
|
|
||||||
const {
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
data,
|
|
||||||
disableEmail,
|
|
||||||
expiration,
|
|
||||||
req: {
|
|
||||||
payload: { config, emailOptions, sendEmail: email },
|
|
||||||
payload,
|
|
||||||
t,
|
|
||||||
},
|
|
||||||
req,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection?.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'forgotPassword',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
}, Promise.resolve())
|
||||||
|
|
||||||
|
const {
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
data,
|
||||||
|
disableEmail,
|
||||||
|
expiration,
|
||||||
|
req: {
|
||||||
|
payload: { config, emailOptions, sendEmail: email },
|
||||||
|
payload,
|
||||||
|
t,
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
} = args
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// Forget password
|
// Forget password
|
||||||
@@ -159,7 +160,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
|
|||||||
|
|
||||||
return token
|
return token
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import type { PayloadRequest } from '../../../express/types'
|
|||||||
import type { Payload } from '../../../payload'
|
import type { Payload } from '../../../payload'
|
||||||
import type { Result } from '../forgotPassword'
|
import type { Result } from '../forgotPassword'
|
||||||
|
|
||||||
import { getDataLoader } from '../../../collections/dataloader'
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import forgotPassword from '../forgotPassword'
|
import forgotPassword from '../forgotPassword'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -24,15 +22,7 @@ async function localForgotPassword<T extends keyof GeneratedTypes['collections']
|
|||||||
payload: Payload,
|
payload: Payload,
|
||||||
options: Options<T>,
|
options: Options<T>,
|
||||||
): Promise<Result> {
|
): Promise<Result> {
|
||||||
const {
|
const { collection: collectionSlug, data, disableEmail, expiration } = options
|
||||||
collection: collectionSlug,
|
|
||||||
context,
|
|
||||||
data,
|
|
||||||
disableEmail,
|
|
||||||
expiration,
|
|
||||||
req = {} as PayloadRequest,
|
|
||||||
} = options
|
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
|
|
||||||
@@ -44,12 +34,7 @@ async function localForgotPassword<T extends keyof GeneratedTypes['collections']
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
const req = createLocalReq(options, payload)
|
||||||
req.payload = payload
|
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
return forgotPassword({
|
return forgotPassword({
|
||||||
collection,
|
collection,
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ import type { GeneratedTypes } from '../../../index'
|
|||||||
import type { Payload } from '../../../payload'
|
import type { Payload } from '../../../payload'
|
||||||
import type { Result } from '../login'
|
import type { Result } from '../login'
|
||||||
|
|
||||||
import { getDataLoader } from '../../../collections/dataloader'
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import login from '../login'
|
import login from '../login'
|
||||||
|
|
||||||
export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
|
export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -33,25 +31,14 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
): Promise<Result & { user: GeneratedTypes['collections'][TSlug] }> {
|
): Promise<Result & { user: GeneratedTypes['collections'][TSlug] }> {
|
||||||
const {
|
const {
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
context,
|
|
||||||
data,
|
data,
|
||||||
depth,
|
depth,
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
req = {} as PayloadRequest,
|
|
||||||
res,
|
res,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
} = options
|
} = options
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || req?.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -59,12 +46,7 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
const req = createLocalReq(options, payload)
|
||||||
req.payload = payload
|
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
collection,
|
collection,
|
||||||
@@ -76,12 +58,6 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (locale) args.req.locale = locale
|
|
||||||
if (fallbackLocale) {
|
|
||||||
args.req.fallbackLocale =
|
|
||||||
typeof fallbackLocaleArg !== 'undefined' ? fallbackLocaleArg : fallbackLocale || defaultLocale
|
|
||||||
}
|
|
||||||
|
|
||||||
return login<TSlug>(args)
|
return login<TSlug>(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import type { PayloadRequest } from '../../../express/types'
|
|||||||
import type { Payload } from '../../../payload'
|
import type { Payload } from '../../../payload'
|
||||||
import type { Result } from '../resetPassword'
|
import type { Result } from '../resetPassword'
|
||||||
|
|
||||||
import { getDataLoader } from '../../../collections/dataloader'
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import resetPassword from '../resetPassword'
|
import resetPassword from '../resetPassword'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -24,15 +22,7 @@ async function localResetPassword<T extends keyof GeneratedTypes['collections']>
|
|||||||
payload: Payload,
|
payload: Payload,
|
||||||
options: Options<T>,
|
options: Options<T>,
|
||||||
): Promise<Result> {
|
): Promise<Result> {
|
||||||
const {
|
const { collection: collectionSlug, data, overrideAccess } = options
|
||||||
collection: collectionSlug,
|
|
||||||
context,
|
|
||||||
data,
|
|
||||||
overrideAccess,
|
|
||||||
req = {} as PayloadRequest,
|
|
||||||
} = options
|
|
||||||
|
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
|
|
||||||
@@ -44,12 +34,7 @@ async function localResetPassword<T extends keyof GeneratedTypes['collections']>
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payload = payload
|
const req = createLocalReq(options, payload)
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
return resetPassword({
|
return resetPassword({
|
||||||
collection,
|
collection,
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ import type { GeneratedTypes, RequestContext } from '../../../'
|
|||||||
import type { PayloadRequest } from '../../../express/types'
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
import type { Payload } from '../../../payload'
|
import type { Payload } from '../../../payload'
|
||||||
|
|
||||||
import { getDataLoader } from '../../../collections/dataloader'
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import unlock from '../unlock'
|
import unlock from '../unlock'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -22,14 +20,7 @@ async function localUnlock<T extends keyof GeneratedTypes['collections']>(
|
|||||||
payload: Payload,
|
payload: Payload,
|
||||||
options: Options<T>,
|
options: Options<T>,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const {
|
const { collection: collectionSlug, data, overrideAccess = true } = options
|
||||||
collection: collectionSlug,
|
|
||||||
context,
|
|
||||||
data,
|
|
||||||
overrideAccess = true,
|
|
||||||
req = {} as PayloadRequest,
|
|
||||||
} = options
|
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
|
|
||||||
@@ -39,12 +30,7 @@ async function localUnlock<T extends keyof GeneratedTypes['collections']>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payload = payload
|
const req = createLocalReq(options, payload)
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
return unlock({
|
return unlock({
|
||||||
collection,
|
collection,
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import type { PayloadRequest } from '../../../express/types'
|
|||||||
import type { Payload } from '../../../payload'
|
import type { Payload } from '../../../payload'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import verifyEmail from '../verifyEmail'
|
import verifyEmail from '../verifyEmail'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -18,8 +17,7 @@ async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
|
|||||||
payload: Payload,
|
payload: Payload,
|
||||||
options: Options<T>,
|
options: Options<T>,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const { collection: collectionSlug, context, req = {} as PayloadRequest, token } = options
|
const { collection: collectionSlug, token } = options
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
|
|
||||||
@@ -29,9 +27,7 @@ async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payload = payload
|
const req = createLocalReq(options, payload)
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
|
||||||
|
|
||||||
return verifyEmail({
|
return verifyEmail({
|
||||||
collection,
|
collection,
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'
|
|||||||
import isLocked from '../isLocked'
|
import isLocked from '../isLocked'
|
||||||
import { authenticateLocalStrategy } from '../strategies/local/authenticate'
|
import { authenticateLocalStrategy } from '../strategies/local/authenticate'
|
||||||
import { incrementLoginAttempts } from '../strategies/local/incrementLoginAttempts'
|
import { incrementLoginAttempts } from '../strategies/local/incrementLoginAttempts'
|
||||||
|
import { resetLoginAttempts } from '../strategies/local/resetLoginAttempts'
|
||||||
import { getFieldsToSign } from './getFieldsToSign'
|
import { getFieldsToSign } from './getFieldsToSign'
|
||||||
import unlock from './unlock'
|
|
||||||
|
|
||||||
export type Result = {
|
export type Result = {
|
||||||
exp?: number
|
exp?: number
|
||||||
@@ -45,37 +45,38 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
): Promise<Result & { user: GeneratedTypes['collections'][TSlug] }> {
|
): Promise<Result & { user: GeneratedTypes['collections'][TSlug] }> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection?.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'login',
|
|
||||||
})) || args
|
|
||||||
}, Promise.resolve())
|
|
||||||
|
|
||||||
const {
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
data,
|
|
||||||
depth,
|
|
||||||
overrideAccess,
|
|
||||||
req,
|
|
||||||
req: {
|
|
||||||
payload,
|
|
||||||
payload: { config, secret },
|
|
||||||
},
|
|
||||||
showHiddenFields,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection?.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'login',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
}, Promise.resolve())
|
||||||
|
|
||||||
|
const {
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
data,
|
||||||
|
depth,
|
||||||
|
overrideAccess,
|
||||||
|
req,
|
||||||
|
req: {
|
||||||
|
payload,
|
||||||
|
payload: { config, secret },
|
||||||
|
},
|
||||||
|
showHiddenFields,
|
||||||
|
} = args
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// Login
|
// Login
|
||||||
@@ -115,16 +116,16 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldCommit) await commitTransaction(req)
|
||||||
|
|
||||||
throw new AuthenticationError(req.t)
|
throw new AuthenticationError(req.t)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxLoginAttemptsEnabled) {
|
if (maxLoginAttemptsEnabled) {
|
||||||
await unlock({
|
await resetLoginAttempts({
|
||||||
collection: {
|
collection: collectionConfig,
|
||||||
config: collectionConfig,
|
doc: user,
|
||||||
},
|
payload: req.payload,
|
||||||
data,
|
|
||||||
overrideAccess: true,
|
|
||||||
req,
|
req,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -262,7 +263,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import type { Document } from '../../types'
|
|||||||
|
|
||||||
import { buildAfterOperation } from '../../collections/operations/utils'
|
import { buildAfterOperation } from '../../collections/operations/utils'
|
||||||
import { Forbidden } from '../../errors'
|
import { Forbidden } from '../../errors'
|
||||||
|
import { commitTransaction } from '../../utilities/commitTransaction'
|
||||||
import getCookieExpiration from '../../utilities/getCookieExpiration'
|
import getCookieExpiration from '../../utilities/getCookieExpiration'
|
||||||
|
import { initTransaction } from '../../utilities/initTransaction'
|
||||||
|
import { killTransaction } from '../../utilities/killTransaction'
|
||||||
import { getFieldsToSign } from './getFieldsToSign'
|
import { getFieldsToSign } from './getFieldsToSign'
|
||||||
|
|
||||||
export type Result = {
|
export type Result = {
|
||||||
@@ -28,120 +31,130 @@ export type Arguments = {
|
|||||||
async function refresh(incomingArgs: Arguments): Promise<Result> {
|
async function refresh(incomingArgs: Arguments): Promise<Result> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
try {
|
||||||
// beforeOperation - Collection
|
const shouldCommit = await initTransaction(args.req)
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(
|
// /////////////////////////////////////
|
||||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
// beforeOperation - Collection
|
||||||
await priorHook
|
// /////////////////////////////////////
|
||||||
|
|
||||||
args =
|
await args.collection.config.hooks.beforeOperation.reduce(
|
||||||
(await hook({
|
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||||
args,
|
await priorHook
|
||||||
collection: args.collection?.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'refresh',
|
|
||||||
})) || args
|
|
||||||
},
|
|
||||||
Promise.resolve(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// /////////////////////////////////////
|
args =
|
||||||
// Refresh
|
(await hook({
|
||||||
// /////////////////////////////////////
|
args,
|
||||||
|
collection: args.collection?.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'refresh',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
},
|
||||||
|
Promise.resolve(),
|
||||||
|
)
|
||||||
|
|
||||||
const {
|
// /////////////////////////////////////
|
||||||
collection: { config: collectionConfig },
|
// Refresh
|
||||||
req: {
|
// /////////////////////////////////////
|
||||||
payload: { config, secret },
|
|
||||||
},
|
|
||||||
} = args
|
|
||||||
|
|
||||||
if (typeof args.token !== 'string' || !args.req.user) throw new Forbidden(args.req.t)
|
const {
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
req: {
|
||||||
|
payload: { config, secret },
|
||||||
|
},
|
||||||
|
} = args
|
||||||
|
|
||||||
const parsedURL = url.parse(args.req.url)
|
if (typeof args.token !== 'string' || !args.req.user) throw new Forbidden(args.req.t)
|
||||||
const isGraphQL = parsedURL.pathname === config.routes.graphQL
|
|
||||||
|
|
||||||
const user = await args.req.payload.findByID({
|
const parsedURL = url.parse(args.req.url)
|
||||||
id: args.req.user.id,
|
const isGraphQL = parsedURL.pathname === config.routes.graphQL
|
||||||
collection: args.req.user.collection,
|
|
||||||
depth: isGraphQL ? 0 : args.collection.config.auth.depth,
|
|
||||||
req: args.req,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fieldsToSign = getFieldsToSign({
|
const user = await args.req.payload.findByID({
|
||||||
collectionConfig,
|
id: args.req.user.id,
|
||||||
email: user?.email as string,
|
collection: args.req.user.collection,
|
||||||
user: args?.req?.user,
|
depth: isGraphQL ? 0 : args.collection.config.auth.depth,
|
||||||
})
|
req: args.req,
|
||||||
|
})
|
||||||
|
|
||||||
const refreshedToken = jwt.sign(fieldsToSign, secret, {
|
const fieldsToSign = getFieldsToSign({
|
||||||
expiresIn: collectionConfig.auth.tokenExpiration,
|
collectionConfig,
|
||||||
})
|
email: user?.email as string,
|
||||||
|
user: args?.req?.user,
|
||||||
|
})
|
||||||
|
|
||||||
const exp = (jwt.decode(refreshedToken) as Record<string, unknown>).exp as number
|
const refreshedToken = jwt.sign(fieldsToSign, secret, {
|
||||||
|
expiresIn: collectionConfig.auth.tokenExpiration,
|
||||||
|
})
|
||||||
|
|
||||||
if (args.res) {
|
const exp = (jwt.decode(refreshedToken) as Record<string, unknown>).exp as number
|
||||||
const cookieOptions = {
|
|
||||||
domain: undefined,
|
if (args.res) {
|
||||||
expires: getCookieExpiration(collectionConfig.auth.tokenExpiration),
|
const cookieOptions = {
|
||||||
httpOnly: true,
|
domain: undefined,
|
||||||
path: '/',
|
expires: getCookieExpiration(collectionConfig.auth.tokenExpiration),
|
||||||
sameSite: collectionConfig.auth.cookies.sameSite,
|
httpOnly: true,
|
||||||
secure: collectionConfig.auth.cookies.secure,
|
path: '/',
|
||||||
|
sameSite: collectionConfig.auth.cookies.sameSite,
|
||||||
|
secure: collectionConfig.auth.cookies.secure,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionConfig.auth.cookies.domain)
|
||||||
|
cookieOptions.domain = collectionConfig.auth.cookies.domain
|
||||||
|
|
||||||
|
args.res.cookie(`${config.cookiePrefix}-token`, refreshedToken, cookieOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collectionConfig.auth.cookies.domain)
|
let result: Result = {
|
||||||
cookieOptions.domain = collectionConfig.auth.cookies.domain
|
exp,
|
||||||
|
refreshedToken,
|
||||||
|
user,
|
||||||
|
}
|
||||||
|
|
||||||
args.res.cookie(`${config.cookiePrefix}-token`, refreshedToken, cookieOptions)
|
// /////////////////////////////////////
|
||||||
|
// After Refresh - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await collectionConfig.hooks.afterRefresh.reduce(async (priorHook, hook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
result =
|
||||||
|
(await hook({
|
||||||
|
collection: args.collection?.config,
|
||||||
|
context: args.req.context,
|
||||||
|
exp,
|
||||||
|
req: args.req,
|
||||||
|
res: args.res,
|
||||||
|
token: refreshedToken,
|
||||||
|
})) || result
|
||||||
|
}, Promise.resolve())
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// afterOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
result = await buildAfterOperation({
|
||||||
|
args,
|
||||||
|
collection: args.collection?.config,
|
||||||
|
operation: 'refresh',
|
||||||
|
result,
|
||||||
|
})
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// Return results
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
if (collectionConfig.auth.removeTokenFromResponses) {
|
||||||
|
delete result.refreshedToken
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldCommit) await commitTransaction(args.req)
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (error: unknown) {
|
||||||
|
await killTransaction(args.req)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: Result = {
|
|
||||||
exp,
|
|
||||||
refreshedToken,
|
|
||||||
user,
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// After Refresh - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await collectionConfig.hooks.afterRefresh.reduce(async (priorHook, hook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
result =
|
|
||||||
(await hook({
|
|
||||||
collection: args.collection?.config,
|
|
||||||
context: args.req.context,
|
|
||||||
exp,
|
|
||||||
req: args.req,
|
|
||||||
res: args.res,
|
|
||||||
token: refreshedToken,
|
|
||||||
})) || result
|
|
||||||
}, Promise.resolve())
|
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// afterOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
result = await buildAfterOperation({
|
|
||||||
args,
|
|
||||||
collection: args.collection?.config,
|
|
||||||
operation: 'refresh',
|
|
||||||
result,
|
|
||||||
})
|
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// Return results
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
if (collectionConfig.auth.removeTokenFromResponses) {
|
|
||||||
delete result.refreshedToken
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default refresh
|
export default refresh
|
||||||
|
|||||||
@@ -52,5 +52,6 @@ export const incrementLoginAttempts = async ({
|
|||||||
id: doc.id,
|
id: doc.id,
|
||||||
collection: collection.slug,
|
collection: collection.slug,
|
||||||
data,
|
data,
|
||||||
|
req,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const resetLoginAttempts = async ({
|
|||||||
payload,
|
payload,
|
||||||
req,
|
req,
|
||||||
}: Args): Promise<void> => {
|
}: Args): Promise<void> => {
|
||||||
|
if (!('lockUntil' in doc && typeof doc.lockUntil === 'string') || doc.loginAttempts === 0) return
|
||||||
await payload.update({
|
await payload.update({
|
||||||
id: doc.id,
|
id: doc.id,
|
||||||
collection: collection.slug,
|
collection: collection.slug,
|
||||||
@@ -22,6 +23,7 @@ export const resetLoginAttempts = async ({
|
|||||||
lockUntil: null,
|
lockUntil: null,
|
||||||
loginAttempts: 0,
|
loginAttempts: 0,
|
||||||
},
|
},
|
||||||
|
overrideAccess: true,
|
||||||
req,
|
req,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ export async function generateTypes(): Promise<void> {
|
|||||||
style: {
|
style: {
|
||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
},
|
},
|
||||||
|
// Generates code for $defs that aren't referenced by the schema. Reason:
|
||||||
|
// If a field defines an interfaceName, it should be included in the generated types
|
||||||
|
// even if it's not used by another type. Reason: the user might want to use it in their own code.
|
||||||
|
unreachableDefinitions: true,
|
||||||
}).then((compiled) => {
|
}).then((compiled) => {
|
||||||
if (config.typescript.declare !== false) {
|
if (config.typescript.declare !== false) {
|
||||||
compiled += `\n\n${declare}`
|
compiled += `\n\n${declare}`
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ if (tsConfig?.config?.compilerOptions?.paths) {
|
|||||||
// Allow disabling SWC for debugging
|
// Allow disabling SWC for debugging
|
||||||
if (process.env.DISABLE_SWC !== 'true') {
|
if (process.env.DISABLE_SWC !== 'true') {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore - bad @swc/register types
|
// @ts-expect-error - bad @swc/register types
|
||||||
swcRegister(swcOptions)
|
swcRegister(swcOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const availableCommands = [
|
|||||||
const availableCommandsMsg = `Available commands: ${availableCommands.join(', ')}`
|
const availableCommandsMsg = `Available commands: ${availableCommands.join(', ')}`
|
||||||
|
|
||||||
export const migrate = async (parsedArgs: ParsedArgs): Promise<void> => {
|
export const migrate = async (parsedArgs: ParsedArgs): Promise<void> => {
|
||||||
const { _: args, file, help } = parsedArgs
|
const { _: args, file, forceAcceptWarning, help } = parsedArgs
|
||||||
if (help) {
|
if (help) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`\n\n${availableCommandsMsg}\n`) // Avoid having to init payload to get the logger
|
console.log(`\n\n${availableCommandsMsg}\n`) // Avoid having to init payload to get the logger
|
||||||
@@ -74,11 +74,16 @@ export const migrate = async (parsedArgs: ParsedArgs): Promise<void> => {
|
|||||||
await adapter.migrateReset()
|
await adapter.migrateReset()
|
||||||
break
|
break
|
||||||
case 'migrate:fresh':
|
case 'migrate:fresh':
|
||||||
await adapter.migrateFresh()
|
await adapter.migrateFresh({ forceAcceptWarning })
|
||||||
break
|
break
|
||||||
case 'migrate:create':
|
case 'migrate:create':
|
||||||
try {
|
try {
|
||||||
await adapter.createMigration({ file, migrationName: args[1], payload })
|
await adapter.createMigration({
|
||||||
|
file,
|
||||||
|
forceAcceptWarning,
|
||||||
|
migrationName: args[1],
|
||||||
|
payload,
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Error creating migration: ${err.message}`)
|
throw new Error(`Error creating migration: ${err.message}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export type BeforeOperationHook = (args: {
|
|||||||
* Hook operation being performed
|
* Hook operation being performed
|
||||||
*/
|
*/
|
||||||
operation: HookOperationType
|
operation: HookOperationType
|
||||||
|
req: PayloadRequest
|
||||||
}) => any
|
}) => any
|
||||||
|
|
||||||
export type BeforeValidateHook<T extends TypeWithID = any> = (args: {
|
export type BeforeValidateHook<T extends TypeWithID = any> = (args: {
|
||||||
|
|||||||
@@ -55,44 +55,45 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(
|
|
||||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'create',
|
|
||||||
})) || args
|
|
||||||
},
|
|
||||||
Promise.resolve(),
|
|
||||||
)
|
|
||||||
|
|
||||||
const {
|
|
||||||
autosave = false,
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
collection,
|
|
||||||
depth,
|
|
||||||
disableVerificationEmail,
|
|
||||||
draft = false,
|
|
||||||
overrideAccess,
|
|
||||||
overwriteExistingFiles = false,
|
|
||||||
req: {
|
|
||||||
payload,
|
|
||||||
payload: { config, emailOptions },
|
|
||||||
},
|
|
||||||
req,
|
|
||||||
showHiddenFields,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(
|
||||||
|
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'create',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
},
|
||||||
|
Promise.resolve(),
|
||||||
|
)
|
||||||
|
|
||||||
|
const {
|
||||||
|
autosave = false,
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
collection,
|
||||||
|
depth,
|
||||||
|
disableVerificationEmail,
|
||||||
|
draft = false,
|
||||||
|
overrideAccess,
|
||||||
|
overwriteExistingFiles = false,
|
||||||
|
req: {
|
||||||
|
payload,
|
||||||
|
payload: { config, emailOptions },
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
showHiddenFields,
|
||||||
|
} = args
|
||||||
|
|
||||||
let { data } = args
|
let { data } = args
|
||||||
|
|
||||||
@@ -367,7 +368,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,42 +39,42 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
|||||||
}> {
|
}> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(
|
|
||||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'delete',
|
|
||||||
})) || args
|
|
||||||
},
|
|
||||||
Promise.resolve(),
|
|
||||||
)
|
|
||||||
|
|
||||||
const {
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
depth,
|
|
||||||
overrideAccess,
|
|
||||||
req: {
|
|
||||||
locale,
|
|
||||||
payload: { config },
|
|
||||||
payload,
|
|
||||||
t,
|
|
||||||
},
|
|
||||||
req,
|
|
||||||
showHiddenFields,
|
|
||||||
where,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(
|
||||||
|
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'delete',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
},
|
||||||
|
Promise.resolve(),
|
||||||
|
)
|
||||||
|
|
||||||
|
const {
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
depth,
|
||||||
|
overrideAccess,
|
||||||
|
req: {
|
||||||
|
locale,
|
||||||
|
payload: { config },
|
||||||
|
payload,
|
||||||
|
t,
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
showHiddenFields,
|
||||||
|
where,
|
||||||
|
} = args
|
||||||
|
|
||||||
if (!where) {
|
if (!where) {
|
||||||
throw new APIError("Missing 'where' query of documents to delete.", httpStatus.BAD_REQUEST)
|
throw new APIError("Missing 'where' query of documents to delete.", httpStatus.BAD_REQUEST)
|
||||||
@@ -264,7 +264,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,41 +30,42 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
): Promise<Document> {
|
): Promise<Document> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(
|
|
||||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'delete',
|
|
||||||
})) || args
|
|
||||||
},
|
|
||||||
Promise.resolve(),
|
|
||||||
)
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
depth,
|
|
||||||
overrideAccess,
|
|
||||||
req: {
|
|
||||||
payload: { config },
|
|
||||||
payload,
|
|
||||||
t,
|
|
||||||
},
|
|
||||||
req,
|
|
||||||
showHiddenFields,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(
|
||||||
|
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'delete',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
},
|
||||||
|
Promise.resolve(),
|
||||||
|
)
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
depth,
|
||||||
|
overrideAccess,
|
||||||
|
req: {
|
||||||
|
payload: { config },
|
||||||
|
payload,
|
||||||
|
t,
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
showHiddenFields,
|
||||||
|
} = args
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// Access
|
// Access
|
||||||
@@ -213,7 +214,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,42 +37,43 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
|||||||
): Promise<PaginatedDocs<T>> {
|
): Promise<PaginatedDocs<T>> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'read',
|
|
||||||
})) || args
|
|
||||||
}, Promise.resolve())
|
|
||||||
|
|
||||||
const {
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
collection,
|
|
||||||
currentDepth,
|
|
||||||
depth,
|
|
||||||
disableErrors,
|
|
||||||
draft: draftsEnabled,
|
|
||||||
limit,
|
|
||||||
overrideAccess,
|
|
||||||
page,
|
|
||||||
pagination = true,
|
|
||||||
req: { locale, payload },
|
|
||||||
req,
|
|
||||||
showHiddenFields,
|
|
||||||
sort,
|
|
||||||
where,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'read',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
}, Promise.resolve())
|
||||||
|
|
||||||
|
const {
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
collection,
|
||||||
|
currentDepth,
|
||||||
|
depth,
|
||||||
|
disableErrors,
|
||||||
|
draft: draftsEnabled,
|
||||||
|
limit,
|
||||||
|
overrideAccess,
|
||||||
|
page,
|
||||||
|
pagination = true,
|
||||||
|
req: { locale, payload },
|
||||||
|
req,
|
||||||
|
showHiddenFields,
|
||||||
|
sort,
|
||||||
|
where,
|
||||||
|
} = args
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// Access
|
// Access
|
||||||
@@ -253,7 +254,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,38 +30,39 @@ export type Arguments = {
|
|||||||
async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<T> {
|
async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<T> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'read',
|
|
||||||
})) || args
|
|
||||||
}, Promise.resolve())
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
currentDepth,
|
|
||||||
depth,
|
|
||||||
disableErrors,
|
|
||||||
draft: draftEnabled = false,
|
|
||||||
overrideAccess = false,
|
|
||||||
req: { locale, t },
|
|
||||||
req,
|
|
||||||
showHiddenFields,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
const { transactionID } = req
|
const { transactionID } = args.req
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'read',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
}, Promise.resolve())
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
currentDepth,
|
||||||
|
depth,
|
||||||
|
disableErrors,
|
||||||
|
draft: draftEnabled = false,
|
||||||
|
overrideAccess = false,
|
||||||
|
req: { locale, t },
|
||||||
|
req,
|
||||||
|
showHiddenFields,
|
||||||
|
} = args
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// Access
|
// Access
|
||||||
@@ -204,7 +205,7 @@ async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import type { Document } from '../../../types'
|
|||||||
import type { File } from '../../../uploads/types'
|
import type { File } from '../../../uploads/types'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import getFileByPath from '../../../uploads/getFileByPath'
|
import getFileByPath from '../../../uploads/getFileByPath'
|
||||||
import { getDataLoader } from '../../dataloader'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import create from '../create'
|
import create from '../create'
|
||||||
|
|
||||||
export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
|
export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -44,30 +42,17 @@ export default async function createLocal<TSlug extends keyof GeneratedTypes['co
|
|||||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||||
const {
|
const {
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
context,
|
|
||||||
data,
|
data,
|
||||||
depth,
|
depth,
|
||||||
disableVerificationEmail,
|
disableVerificationEmail,
|
||||||
draft,
|
draft,
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
file,
|
file,
|
||||||
filePath,
|
filePath,
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
overwriteExistingFiles = false,
|
overwriteExistingFiles = false,
|
||||||
req = {} as PayloadRequest,
|
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
user,
|
|
||||||
} = options
|
} = options
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || req.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -75,21 +60,18 @@ export default async function createLocal<TSlug extends keyof GeneratedTypes['co
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
const req = createLocalReq(options, payload)
|
||||||
req.locale = locale
|
const fileToSet = (file ?? (await getFileByPath(filePath))) as UploadedFile
|
||||||
req.fallbackLocale =
|
if (fileToSet) {
|
||||||
typeof fallbackLocaleArg !== 'undefined' ? fallbackLocaleArg : fallbackLocale || defaultLocale
|
if (req?.files) {
|
||||||
req.payload = payload
|
req.files.file = fileToSet
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
} else {
|
||||||
req.files = {
|
req.files = {
|
||||||
file: (file ?? (await getFileByPath(filePath))) as UploadedFile,
|
file: fileToSet,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof user !== 'undefined') req.user = user
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
return create<TSlug>({
|
return create<TSlug>({
|
||||||
collection,
|
collection,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import type { Document, Where } from '../../../types'
|
|||||||
import type { BulkOperationResult } from '../../config/types'
|
import type { BulkOperationResult } from '../../config/types'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import { getDataLoader } from '../../dataloader'
|
|
||||||
import deleteOperation from '../delete'
|
import deleteOperation from '../delete'
|
||||||
import deleteByID from '../deleteByID'
|
import deleteByID from '../deleteByID'
|
||||||
|
|
||||||
@@ -59,24 +57,13 @@ async function deleteLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
context,
|
|
||||||
depth,
|
depth,
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
req: incomingReq = {} as PayloadRequest,
|
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
user,
|
|
||||||
where,
|
where,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || incomingReq?.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -84,22 +71,7 @@ async function deleteLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = {
|
const req = createLocalReq(options, payload)
|
||||||
fallbackLocale:
|
|
||||||
typeof fallbackLocaleArg !== 'undefined'
|
|
||||||
? fallbackLocaleArg
|
|
||||||
: fallbackLocale || defaultLocale,
|
|
||||||
i18n: i18nInit(payload.config.i18n),
|
|
||||||
locale: locale,
|
|
||||||
payload,
|
|
||||||
payloadAPI: 'local',
|
|
||||||
transactionID: incomingReq?.transactionID,
|
|
||||||
user,
|
|
||||||
} as PayloadRequest
|
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import type { Payload } from '../../../payload'
|
|||||||
import type { Document, Where } from '../../../types'
|
import type { Document, Where } from '../../../types'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import { getDataLoader } from '../../dataloader'
|
|
||||||
import find from '../find'
|
import find from '../find'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -39,32 +37,20 @@ export default async function findLocal<T extends keyof GeneratedTypes['collecti
|
|||||||
): Promise<PaginatedDocs<GeneratedTypes['collections'][T]>> {
|
): Promise<PaginatedDocs<GeneratedTypes['collections'][T]>> {
|
||||||
const {
|
const {
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
context,
|
|
||||||
currentDepth,
|
currentDepth,
|
||||||
depth,
|
depth,
|
||||||
disableErrors,
|
disableErrors,
|
||||||
draft = false,
|
draft = false,
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
limit,
|
limit,
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
page,
|
page,
|
||||||
pagination = true,
|
pagination = true,
|
||||||
req = {} as PayloadRequest,
|
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
sort,
|
sort,
|
||||||
user,
|
|
||||||
where,
|
where,
|
||||||
} = options
|
} = options
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || req.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -72,17 +58,7 @@ export default async function findLocal<T extends keyof GeneratedTypes['collecti
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
const req = createLocalReq(options, payload)
|
||||||
req.locale = locale
|
|
||||||
req.fallbackLocale =
|
|
||||||
typeof fallbackLocaleArg !== 'undefined' ? fallbackLocaleArg : fallbackLocale || defaultLocale
|
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
|
||||||
req.payload = payload
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
if (typeof user !== 'undefined') req.user = user
|
|
||||||
|
|
||||||
return find<GeneratedTypes['collections'][T]>({
|
return find<GeneratedTypes['collections'][T]>({
|
||||||
collection,
|
collection,
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import type { Payload } from '../../../payload'
|
|||||||
import type { Document } from '../../../types'
|
import type { Document } from '../../../types'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import { getDataLoader } from '../../dataloader'
|
|
||||||
import findByID from '../findByID'
|
import findByID from '../findByID'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -35,27 +33,15 @@ export default async function findByIDLocal<T extends keyof GeneratedTypes['coll
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
context,
|
|
||||||
currentDepth,
|
currentDepth,
|
||||||
depth,
|
depth,
|
||||||
disableErrors = false,
|
disableErrors = false,
|
||||||
draft = false,
|
draft = false,
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
req = {} as PayloadRequest,
|
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
user,
|
|
||||||
} = options
|
} = options
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || req.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -63,17 +49,7 @@ export default async function findByIDLocal<T extends keyof GeneratedTypes['coll
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
const req = createLocalReq(options, payload)
|
||||||
req.locale = locale
|
|
||||||
req.fallbackLocale =
|
|
||||||
typeof fallbackLocaleArg !== 'undefined' ? fallbackLocaleArg : fallbackLocale || defaultLocale
|
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
|
||||||
req.payload = payload
|
|
||||||
|
|
||||||
if (typeof user !== 'undefined') req.user = user
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
return findByID<GeneratedTypes['collections'][T]>({
|
return findByID<GeneratedTypes['collections'][T]>({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import type { Document } from '../../../types'
|
|||||||
import type { TypeWithVersion } from '../../../versions/types'
|
import type { TypeWithVersion } from '../../../versions/types'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import { getDataLoader } from '../../dataloader'
|
|
||||||
import findVersionByID from '../findVersionByID'
|
import findVersionByID from '../findVersionByID'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -35,24 +33,13 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
context,
|
|
||||||
depth,
|
depth,
|
||||||
disableErrors = false,
|
disableErrors = false,
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
req = {} as PayloadRequest,
|
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
} = options
|
} = options
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || req.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -62,15 +49,7 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.payloadAPI = req.payloadAPI || 'local'
|
const req = createLocalReq(options, payload)
|
||||||
req.locale = locale
|
|
||||||
req.fallbackLocale =
|
|
||||||
typeof fallbackLocaleArg !== 'undefined' ? fallbackLocaleArg : fallbackLocale || defaultLocale
|
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
|
||||||
req.payload = payload
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
return findVersionByID({
|
return findVersionByID({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import type { Document, Where } from '../../../types'
|
|||||||
import type { TypeWithVersion } from '../../../versions/types'
|
import type { TypeWithVersion } from '../../../versions/types'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import { getDataLoader } from '../../dataloader'
|
|
||||||
import findVersions from '../findVersions'
|
import findVersions from '../findVersions'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -37,27 +35,16 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
|||||||
): Promise<PaginatedDocs<TypeWithVersion<GeneratedTypes['collections'][T]>>> {
|
): Promise<PaginatedDocs<TypeWithVersion<GeneratedTypes['collections'][T]>>> {
|
||||||
const {
|
const {
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
context,
|
|
||||||
depth,
|
depth,
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
limit,
|
limit,
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
page,
|
page,
|
||||||
req: incomingReq,
|
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
sort,
|
sort,
|
||||||
user,
|
|
||||||
where,
|
where,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || incomingReq?.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -65,23 +52,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = i18nInit(payload.config.i18n)
|
const req = createLocalReq(options, payload)
|
||||||
const req = {
|
|
||||||
fallbackLocale:
|
|
||||||
typeof fallbackLocaleArg !== 'undefined'
|
|
||||||
? fallbackLocaleArg
|
|
||||||
: fallbackLocale || defaultLocale,
|
|
||||||
i18n,
|
|
||||||
locale,
|
|
||||||
payload,
|
|
||||||
payloadAPI: 'local',
|
|
||||||
transactionID: incomingReq?.transactionID,
|
|
||||||
user,
|
|
||||||
} as PayloadRequest
|
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
return findVersions({
|
return findVersions({
|
||||||
collection,
|
collection,
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import type { Payload } from '../../../payload'
|
|||||||
import type { Document } from '../../../types'
|
import type { Document } from '../../../types'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import { getDataLoader } from '../../dataloader'
|
|
||||||
import restoreVersion from '../restoreVersion'
|
import restoreVersion from '../restoreVersion'
|
||||||
|
|
||||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||||
@@ -30,26 +28,9 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
|||||||
payload: Payload,
|
payload: Payload,
|
||||||
options: Options<T>,
|
options: Options<T>,
|
||||||
): Promise<GeneratedTypes['collections'][T]> {
|
): Promise<GeneratedTypes['collections'][T]> {
|
||||||
const {
|
const { id, collection: collectionSlug, depth, overrideAccess = true, showHiddenFields } = options
|
||||||
id,
|
|
||||||
collection: collectionSlug,
|
|
||||||
context,
|
|
||||||
depth,
|
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
|
||||||
req: incomingReq,
|
|
||||||
showHiddenFields,
|
|
||||||
user,
|
|
||||||
} = options
|
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || incomingReq?.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -59,23 +40,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = i18nInit(payload.config.i18n)
|
const req = createLocalReq(options, payload)
|
||||||
const req = {
|
|
||||||
fallbackLocale:
|
|
||||||
typeof fallbackLocaleArg !== 'undefined'
|
|
||||||
? fallbackLocaleArg
|
|
||||||
: fallbackLocale || defaultLocale,
|
|
||||||
i18n,
|
|
||||||
locale,
|
|
||||||
payload,
|
|
||||||
payloadAPI: 'local',
|
|
||||||
t: i18n.t,
|
|
||||||
transactionID: incomingReq?.transactionID,
|
|
||||||
user,
|
|
||||||
} as PayloadRequest
|
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { UploadedFile } from 'express-fileupload'
|
||||||
import type { DeepPartial } from 'ts-essentials'
|
import type { DeepPartial } from 'ts-essentials'
|
||||||
|
|
||||||
import type { GeneratedTypes } from '../../../'
|
import type { GeneratedTypes } from '../../../'
|
||||||
@@ -8,10 +9,8 @@ import type { File } from '../../../uploads/types'
|
|||||||
import type { BulkOperationResult } from '../../config/types'
|
import type { BulkOperationResult } from '../../config/types'
|
||||||
|
|
||||||
import { APIError } from '../../../errors'
|
import { APIError } from '../../../errors'
|
||||||
import { setRequestContext } from '../../../express/setRequestContext'
|
|
||||||
import { i18nInit } from '../../../translations/init'
|
|
||||||
import getFileByPath from '../../../uploads/getFileByPath'
|
import getFileByPath from '../../../uploads/getFileByPath'
|
||||||
import { getDataLoader } from '../../dataloader'
|
import { createLocalReq } from '../../../utilities/createLocalReq'
|
||||||
import update from '../update'
|
import update from '../update'
|
||||||
import updateByID from '../updateByID'
|
import updateByID from '../updateByID'
|
||||||
|
|
||||||
@@ -70,30 +69,18 @@ async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
id,
|
id,
|
||||||
autosave,
|
autosave,
|
||||||
collection: collectionSlug,
|
collection: collectionSlug,
|
||||||
context,
|
|
||||||
data,
|
data,
|
||||||
depth,
|
depth,
|
||||||
draft,
|
draft,
|
||||||
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
|
||||||
file,
|
file,
|
||||||
filePath,
|
filePath,
|
||||||
locale: localeArg = null,
|
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
overwriteExistingFiles = false,
|
overwriteExistingFiles = false,
|
||||||
req: incomingReq,
|
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
user,
|
|
||||||
where,
|
where,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const collection = payload.collections[collectionSlug]
|
const collection = payload.collections[collectionSlug]
|
||||||
const i18n = i18nInit(payload.config.i18n)
|
|
||||||
const localizationConfig = payload?.config?.localization
|
|
||||||
const defaultLocale = localizationConfig ? localizationConfig.defaultLocale : null
|
|
||||||
const locale = localeArg || incomingReq?.locale || defaultLocale
|
|
||||||
const fallbackLocale = localizationConfig
|
|
||||||
? localizationConfig.locales.find(({ code }) => locale === code)?.fallbackLocale
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new APIError(
|
throw new APIError(
|
||||||
@@ -101,25 +88,17 @@ async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = {
|
const req = createLocalReq(options, payload)
|
||||||
fallbackLocale:
|
const fileToSet = (file ?? (await getFileByPath(filePath))) as UploadedFile
|
||||||
typeof fallbackLocaleArg !== 'undefined'
|
if (fileToSet) {
|
||||||
? fallbackLocaleArg
|
if (req?.files) {
|
||||||
: fallbackLocale || defaultLocale,
|
req.files.file = fileToSet
|
||||||
files: {
|
} else {
|
||||||
file: file ?? (await getFileByPath(filePath)),
|
req.files = {
|
||||||
},
|
file: fileToSet,
|
||||||
i18n,
|
}
|
||||||
locale: locale ?? defaultLocale,
|
}
|
||||||
payload,
|
}
|
||||||
payloadAPI: 'local',
|
|
||||||
transactionID: incomingReq?.transactionID,
|
|
||||||
user,
|
|
||||||
} as PayloadRequest
|
|
||||||
setRequestContext(req, context)
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -47,42 +47,43 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
): Promise<BulkOperationResult<TSlug>> {
|
): Promise<BulkOperationResult<TSlug>> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'update',
|
|
||||||
})) || args
|
|
||||||
}, Promise.resolve())
|
|
||||||
|
|
||||||
const {
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
collection,
|
|
||||||
depth,
|
|
||||||
draft: draftArg = false,
|
|
||||||
overrideAccess,
|
|
||||||
overwriteExistingFiles = false,
|
|
||||||
req: {
|
|
||||||
locale,
|
|
||||||
payload: { config },
|
|
||||||
payload,
|
|
||||||
t,
|
|
||||||
},
|
|
||||||
req,
|
|
||||||
showHiddenFields,
|
|
||||||
where,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'update',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
}, Promise.resolve())
|
||||||
|
|
||||||
|
const {
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
collection,
|
||||||
|
depth,
|
||||||
|
draft: draftArg = false,
|
||||||
|
overrideAccess,
|
||||||
|
overwriteExistingFiles = false,
|
||||||
|
req: {
|
||||||
|
locale,
|
||||||
|
payload: { config },
|
||||||
|
payload,
|
||||||
|
t,
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
showHiddenFields,
|
||||||
|
where,
|
||||||
|
} = args
|
||||||
|
|
||||||
if (!where) {
|
if (!where) {
|
||||||
throw new APIError("Missing 'where' query of documents to update.", httpStatus.BAD_REQUEST)
|
throw new APIError("Missing 'where' query of documents to update.", httpStatus.BAD_REQUEST)
|
||||||
@@ -405,7 +406,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,43 +46,44 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
): Promise<GeneratedTypes['collections'][TSlug]> {
|
): Promise<GeneratedTypes['collections'][TSlug]> {
|
||||||
let args = incomingArgs
|
let args = incomingArgs
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// beforeOperation - Collection
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
|
||||||
await priorHook
|
|
||||||
|
|
||||||
args =
|
|
||||||
(await hook({
|
|
||||||
args,
|
|
||||||
collection: args.collection.config,
|
|
||||||
context: args.req.context,
|
|
||||||
operation: 'update',
|
|
||||||
})) || args
|
|
||||||
}, Promise.resolve())
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
autosave = false,
|
|
||||||
collection: { config: collectionConfig },
|
|
||||||
collection,
|
|
||||||
depth,
|
|
||||||
draft: draftArg = false,
|
|
||||||
overrideAccess,
|
|
||||||
overwriteExistingFiles = false,
|
|
||||||
req: {
|
|
||||||
locale,
|
|
||||||
payload: { config },
|
|
||||||
payload,
|
|
||||||
t,
|
|
||||||
},
|
|
||||||
req,
|
|
||||||
showHiddenFields,
|
|
||||||
} = args
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldCommit = await initTransaction(req)
|
const shouldCommit = await initTransaction(args.req)
|
||||||
|
|
||||||
|
// /////////////////////////////////////
|
||||||
|
// beforeOperation - Collection
|
||||||
|
// /////////////////////////////////////
|
||||||
|
|
||||||
|
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||||
|
await priorHook
|
||||||
|
|
||||||
|
args =
|
||||||
|
(await hook({
|
||||||
|
args,
|
||||||
|
collection: args.collection.config,
|
||||||
|
context: args.req.context,
|
||||||
|
operation: 'update',
|
||||||
|
req: args.req,
|
||||||
|
})) || args
|
||||||
|
}, Promise.resolve())
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
autosave = false,
|
||||||
|
collection: { config: collectionConfig },
|
||||||
|
collection,
|
||||||
|
depth,
|
||||||
|
draft: draftArg = false,
|
||||||
|
overrideAccess,
|
||||||
|
overwriteExistingFiles = false,
|
||||||
|
req: {
|
||||||
|
locale,
|
||||||
|
payload: { config },
|
||||||
|
payload,
|
||||||
|
t,
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
showHiddenFields,
|
||||||
|
} = args
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new APIError('Missing ID of document to update.', httpStatus.BAD_REQUEST)
|
throw new APIError('Missing ID of document to update.', httpStatus.BAD_REQUEST)
|
||||||
@@ -376,7 +377,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await killTransaction(req)
|
await killTransaction(args.req)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type forgotPassword from '../../auth/operations/forgotPassword'
|
import type forgotPassword from '../../auth/operations/forgotPassword'
|
||||||
import type login from '../../auth/operations/login'
|
import type login from '../../auth/operations/login'
|
||||||
import type refresh from '../../auth/operations/refresh'
|
import type refresh from '../../auth/operations/refresh'
|
||||||
|
import type { PayloadRequest } from '../../express/types'
|
||||||
import type { AfterOperationHook, SanitizedCollectionConfig, TypeWithID } from '../config/types'
|
import type { AfterOperationHook, SanitizedCollectionConfig, TypeWithID } from '../config/types'
|
||||||
import type create from './create'
|
import type create from './create'
|
||||||
import type deleteOperation from './delete'
|
import type deleteOperation from './delete'
|
||||||
@@ -22,77 +23,62 @@ export type AfterOperationMap<T extends TypeWithID> = {
|
|||||||
update: typeof update // todo: pass correct generic
|
update: typeof update // todo: pass correct generic
|
||||||
updateByID: typeof updateByID // todo: pass correct generic
|
updateByID: typeof updateByID // todo: pass correct generic
|
||||||
}
|
}
|
||||||
export type AfterOperationArg<T extends TypeWithID> =
|
export type AfterOperationArg<T extends TypeWithID> = {
|
||||||
|
/** The collection which this hook is being run on */
|
||||||
|
collection: SanitizedCollectionConfig
|
||||||
|
req: PayloadRequest
|
||||||
|
} & (
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['create']>[0]
|
args: Parameters<AfterOperationMap<T>['create']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'create'
|
operation: 'create'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['create']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['create']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['delete']>[0]
|
args: Parameters<AfterOperationMap<T>['delete']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'delete'
|
operation: 'delete'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['delete']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['delete']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['deleteByID']>[0]
|
args: Parameters<AfterOperationMap<T>['deleteByID']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'deleteByID'
|
operation: 'deleteByID'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['deleteByID']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['deleteByID']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['find']>[0]
|
args: Parameters<AfterOperationMap<T>['find']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'find'
|
operation: 'find'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['find']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['find']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['findByID']>[0]
|
args: Parameters<AfterOperationMap<T>['findByID']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'findByID'
|
operation: 'findByID'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['findByID']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['findByID']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['forgotPassword']>[0]
|
args: Parameters<AfterOperationMap<T>['forgotPassword']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'forgotPassword'
|
operation: 'forgotPassword'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['forgotPassword']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['forgotPassword']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['login']>[0]
|
args: Parameters<AfterOperationMap<T>['login']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'login'
|
operation: 'login'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['login']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['login']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['refresh']>[0]
|
args: Parameters<AfterOperationMap<T>['refresh']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'refresh'
|
operation: 'refresh'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['refresh']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['refresh']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['update']>[0]
|
args: Parameters<AfterOperationMap<T>['update']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'update'
|
operation: 'update'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['update']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['update']>>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
args: Parameters<AfterOperationMap<T>['updateByID']>[0]
|
args: Parameters<AfterOperationMap<T>['updateByID']>[0]
|
||||||
/** The collection which this hook is being run on */
|
|
||||||
collection: SanitizedCollectionConfig
|
|
||||||
operation: 'updateByID'
|
operation: 'updateByID'
|
||||||
result: Awaited<ReturnType<AfterOperationMap<T>['updateByID']>>
|
result: Awaited<ReturnType<AfterOperationMap<T>['updateByID']>>
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// export type AfterOperationHook = typeof buildAfterOperation;
|
// export type AfterOperationHook = typeof buildAfterOperation;
|
||||||
|
|
||||||
@@ -100,7 +86,7 @@ export const buildAfterOperation = async <
|
|||||||
T extends TypeWithID = any,
|
T extends TypeWithID = any,
|
||||||
O extends keyof AfterOperationMap<T> = keyof AfterOperationMap<T>,
|
O extends keyof AfterOperationMap<T> = keyof AfterOperationMap<T>,
|
||||||
>(
|
>(
|
||||||
operationArgs: AfterOperationArg<T> & { operation: O },
|
operationArgs: Omit<AfterOperationArg<T>, 'req'> & { operation: O },
|
||||||
): Promise<Awaited<ReturnType<AfterOperationMap<T>[O]>>> => {
|
): Promise<Awaited<ReturnType<AfterOperationMap<T>[O]>>> => {
|
||||||
const { args, collection, operation, result } = operationArgs
|
const { args, collection, operation, result } = operationArgs
|
||||||
|
|
||||||
@@ -114,6 +100,7 @@ export const buildAfterOperation = async <
|
|||||||
args,
|
args,
|
||||||
collection,
|
collection,
|
||||||
operation,
|
operation,
|
||||||
|
req: args.req,
|
||||||
result: newResult,
|
result: newResult,
|
||||||
} as AfterOperationArg<T>)
|
} as AfterOperationArg<T>)
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type { PayloadRequest } from '../express/types'
|
|||||||
import type { GlobalConfig, SanitizedGlobalConfig } from '../globals/config/types'
|
import type { GlobalConfig, SanitizedGlobalConfig } from '../globals/config/types'
|
||||||
import type { Payload } from '../payload'
|
import type { Payload } from '../payload'
|
||||||
import type { Where } from '../types'
|
import type { Where } from '../types'
|
||||||
|
import type { PayloadLogger } from '../utilities/logger'
|
||||||
|
|
||||||
type Prettify<T> = {
|
type Prettify<T> = {
|
||||||
[K in keyof T]: T[K]
|
[K in keyof T]: T[K]
|
||||||
@@ -155,6 +156,11 @@ export type InitOptions = {
|
|||||||
* See Pino Docs for options: https://getpino.io/#/docs/api?id=options
|
* See Pino Docs for options: https://getpino.io/#/docs/api?id=options
|
||||||
*/
|
*/
|
||||||
loggerOptions?: LoggerOptions
|
loggerOptions?: LoggerOptions
|
||||||
|
/**
|
||||||
|
* A previously instantiated logger instance. Must conform to the PayloadLogger interface which uses Pino
|
||||||
|
* This allows you to bring your own logger instance and let payload use it
|
||||||
|
*/
|
||||||
|
logger?: PayloadLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that is called immediately following startup that receives the Payload instance as it's only argument.
|
* A function that is called immediately following startup that receives the Payload instance as it's only argument.
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function createDatabaseAdapter<T extends BaseDatabaseAdapter>(
|
|||||||
createMigration,
|
createMigration,
|
||||||
migrate,
|
migrate,
|
||||||
migrateDown,
|
migrateDown,
|
||||||
migrateFresh: async () => null,
|
migrateFresh: async ({ forceAcceptWarning = null }) => null,
|
||||||
migrateRefresh,
|
migrateRefresh,
|
||||||
migrateReset,
|
migrateReset,
|
||||||
migrateStatus,
|
migrateStatus,
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ export async function migrateDown(this: BaseDatabaseAdapter): Promise<void> {
|
|||||||
msg: `Rolling back batch ${latestBatch} consisting of ${existingMigrations.length} migration(s).`,
|
msg: `Rolling back batch ${latestBatch} consisting of ${existingMigrations.length} migration(s).`,
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const migration of existingMigrations) {
|
const latestBatchMigrations = existingMigrations.filter(({ batch }) => batch === latestBatch)
|
||||||
|
|
||||||
|
for (const migration of latestBatchMigrations) {
|
||||||
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
|
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
|
||||||
if (!migrationFile) {
|
if (!migrationFile) {
|
||||||
throw new Error(`Migration ${migration.name} not found locally.`)
|
throw new Error(`Migration ${migration.name} not found locally.`)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export interface BaseDatabaseAdapter {
|
|||||||
/**
|
/**
|
||||||
* Drop the current database and run all migrate up functions
|
* Drop the current database and run all migrate up functions
|
||||||
*/
|
*/
|
||||||
migrateFresh: () => Promise<void>
|
migrateFresh: (args: { forceAcceptWarning?: boolean }) => Promise<void>
|
||||||
/**
|
/**
|
||||||
* Run all migration down functions before running up
|
* Run all migration down functions before running up
|
||||||
*/
|
*/
|
||||||
@@ -138,6 +138,10 @@ export type Destroy = (payload: Payload) => Promise<void>
|
|||||||
|
|
||||||
export type CreateMigration = (args: {
|
export type CreateMigration = (args: {
|
||||||
file?: string
|
file?: string
|
||||||
|
/**
|
||||||
|
* Skips the prompt asking to create empty migrations
|
||||||
|
*/
|
||||||
|
forceAcceptWarning?: boolean
|
||||||
migrationName?: string
|
migrationName?: string
|
||||||
payload: Payload
|
payload: Payload
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export {
|
|||||||
fieldHasSubFields,
|
fieldHasSubFields,
|
||||||
fieldIsArrayType,
|
fieldIsArrayType,
|
||||||
fieldIsBlockType,
|
fieldIsBlockType,
|
||||||
|
fieldIsGroupType,
|
||||||
fieldIsLocalized,
|
fieldIsLocalized,
|
||||||
fieldIsPresentationalOnly,
|
fieldIsPresentationalOnly,
|
||||||
fieldSupportsMany,
|
fieldSupportsMany,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export { combineMerge } from '../utilities/combineMerge'
|
|||||||
export {
|
export {
|
||||||
configToJSONSchema,
|
configToJSONSchema,
|
||||||
entityToJSONSchema,
|
entityToJSONSchema,
|
||||||
|
fieldsToJSONSchema,
|
||||||
withNullableJSONSchemaType,
|
withNullableJSONSchemaType,
|
||||||
} from '../utilities/configToJSONSchema'
|
} from '../utilities/configToJSONSchema'
|
||||||
export { createArrayFromCommaDelineated } from '../utilities/createArrayFromCommaDelineated'
|
export { createArrayFromCommaDelineated } from '../utilities/createArrayFromCommaDelineated'
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ export type FieldHookArgs<T extends TypeWithID = any, P = any, S = any> = {
|
|||||||
originalDoc?: T
|
originalDoc?: T
|
||||||
/** The document before changes were applied, only in `afterChange` hooks. */
|
/** The document before changes were applied, only in `afterChange` hooks. */
|
||||||
previousDoc?: T
|
previousDoc?: T
|
||||||
/** The sibling data from the previous document in `afterChange` hook. */
|
/** The sibling data of the document before changes being applied, only in `beforeChange` and `afterChange` hook. */
|
||||||
previousSiblingDoc?: T
|
previousSiblingDoc?: T
|
||||||
|
/** The previous value of the field, before changes, only in `beforeChange` and `afterChange` hooks. */
|
||||||
previousValue?: P
|
previousValue?: P
|
||||||
/** The Express request object. It is mocked for Local API operations. */
|
/** The Express request object. It is mocked for Local API operations. */
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
@@ -692,6 +693,10 @@ export function fieldIsBlockType(field: Field): field is BlockField {
|
|||||||
return field.type === 'blocks'
|
return field.type === 'blocks'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fieldIsGroupType(field: Field): field is GroupField {
|
||||||
|
return field.type === 'group'
|
||||||
|
}
|
||||||
|
|
||||||
export function optionIsObject(option: Option): option is OptionObject {
|
export function optionIsObject(option: Option): option is OptionObject {
|
||||||
return typeof option === 'object'
|
return typeof option === 'object'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ export const promise = async ({
|
|||||||
global,
|
global,
|
||||||
operation,
|
operation,
|
||||||
originalDoc: doc,
|
originalDoc: doc,
|
||||||
|
previousSiblingDoc: siblingDoc,
|
||||||
|
previousValue: siblingDoc[field.name],
|
||||||
req,
|
req,
|
||||||
siblingData,
|
siblingData,
|
||||||
value: siblingData[field.name],
|
value: siblingData[field.name],
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user