Compare commits
94 Commits
db-postgre
...
plugin-nes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d08e85d08c | ||
|
|
abcbf9974d | ||
|
|
d01437d212 | ||
|
|
06729a0a73 | ||
|
|
2bd7822a16 | ||
|
|
bc7daf6b49 | ||
|
|
feab679ef7 | ||
|
|
be39ed4317 | ||
|
|
570e192eb4 | ||
|
|
22f4967dd4 | ||
|
|
4873c36129 | ||
|
|
f0ec21cdda | ||
|
|
da737bdf8e | ||
|
|
40508880c1 | ||
|
|
8f420d841a | ||
|
|
9022e27308 | ||
|
|
acf2e41312 | ||
|
|
6acfae8ee7 | ||
|
|
20bdd91da4 | ||
|
|
50502834c9 | ||
|
|
983733ad74 | ||
|
|
555d02769a | ||
|
|
682eca2186 | ||
|
|
1d14d9f8b8 | ||
|
|
0abaddc2ef | ||
|
|
21b9453cf4 | ||
|
|
136993ec2b | ||
|
|
63bc4cabe1 | ||
|
|
6a8a6e4ef4 | ||
|
|
9828772890 | ||
|
|
6116573164 | ||
|
|
cab6babd60 | ||
|
|
55399424a1 | ||
|
|
28a30120dd | ||
|
|
40a0921597 | ||
|
|
0b80e4a403 | ||
|
|
b378532ddf | ||
|
|
d419275fb5 | ||
|
|
0fb3a9ca89 | ||
|
|
f43cf185d4 | ||
|
|
5d15955f83 | ||
|
|
2d35e06667 | ||
|
|
d2de6db449 | ||
|
|
a3e78161b5 | ||
|
|
d543665995 | ||
|
|
db7dddf1c5 | ||
|
|
3027a03ad1 | ||
|
|
85e38b7cfd | ||
|
|
9090540ece | ||
|
|
46ef284f6b | ||
|
|
0727dcd963 | ||
|
|
52f8d4f9f0 | ||
|
|
f1fa374ed1 | ||
|
|
6b691eee43 | ||
|
|
be3beabb9b | ||
|
|
1fa00cc25c | ||
|
|
f70943524b | ||
|
|
a67080a291 | ||
|
|
69a99445c9 | ||
|
|
00d8480062 | ||
|
|
7424ba9090 | ||
|
|
ec4d2f97cb | ||
|
|
9d9ac0ec28 | ||
|
|
635e7c26e8 | ||
|
|
c4a4678afb | ||
|
|
a5a91c08a9 | ||
|
|
7db58b482b | ||
|
|
1b914083c8 | ||
|
|
657d14c07b | ||
|
|
fbf8ab72a4 | ||
|
|
997f158149 | ||
|
|
c3be5d1d5e | ||
|
|
250bcd8189 | ||
|
|
a71d37b398 | ||
|
|
5c5523195c | ||
|
|
bff4cf518f | ||
|
|
8015e999cd | ||
|
|
0c905f0da7 | ||
|
|
e691a90a4c | ||
|
|
0b2da4fba7 | ||
|
|
1c6d6788a3 | ||
|
|
ecc7978184 | ||
|
|
1b9ee64a67 | ||
|
|
365047a3fb | ||
|
|
42c06acd18 | ||
|
|
f2c8ac4a9a | ||
|
|
35191bdd66 | ||
|
|
98890eee1f | ||
|
|
c703497924 | ||
|
|
5caad706bb | ||
|
|
aa048d5409 | ||
|
|
aafd538cf8 | ||
|
|
1b42bd207d | ||
|
|
9fac2ef24e |
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
@@ -306,9 +306,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
mongodb-version: 6.0
|
mongodb-version: 6.0
|
||||||
|
|
||||||
- name: Build Website
|
- name: Build Template
|
||||||
run: |
|
run: |
|
||||||
cd templates/${{ matrix.template }}
|
cd templates/${{ matrix.template }}
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
yarn install
|
yarn install
|
||||||
yarn build
|
yarn build
|
||||||
|
yarn generate:types
|
||||||
|
|||||||
101
.gitignore
vendored
101
.gitignore
vendored
@@ -1,7 +1,9 @@
|
|||||||
coverage
|
coverage
|
||||||
package-lock.json
|
package-lock.json
|
||||||
dist
|
dist
|
||||||
.idea
|
/.idea/*
|
||||||
|
!/.idea/runConfigurations
|
||||||
|
|
||||||
test-results
|
test-results
|
||||||
.devcontainer
|
.devcontainer
|
||||||
/migrations
|
/migrations
|
||||||
@@ -230,119 +232,24 @@ GitHub.sublime-settings
|
|||||||
.history
|
.history
|
||||||
.ionide
|
.ionide
|
||||||
|
|
||||||
### WebStorm ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
# CMake
|
||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
# File-based project format
|
||||||
*.iws
|
*.iws
|
||||||
|
|
||||||
# IntelliJ
|
# IntelliJ
|
||||||
out/
|
out/
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
# JIRA plugin
|
||||||
atlassian-ide-plugin.xml
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
com_crashlytics_export_strings.xml
|
com_crashlytics_export_strings.xml
|
||||||
crashlytics.properties
|
crashlytics.properties
|
||||||
crashlytics-build.properties
|
crashlytics-build.properties
|
||||||
fabric.properties
|
fabric.properties
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### WebStorm Patch ###
|
|
||||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
|
||||||
|
|
||||||
# *.iml
|
|
||||||
# modules.xml
|
|
||||||
# .idea/misc.xml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# Sonarlint plugin
|
|
||||||
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
|
||||||
.idea/**/sonarlint/
|
|
||||||
|
|
||||||
# SonarQube Plugin
|
|
||||||
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
|
||||||
.idea/**/sonarIssues.xml
|
|
||||||
|
|
||||||
# Markdown Navigator plugin
|
|
||||||
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
|
||||||
.idea/**/markdown-navigator.xml
|
|
||||||
.idea/**/markdown-navigator-enh.xml
|
|
||||||
.idea/**/markdown-navigator/
|
|
||||||
|
|
||||||
# Cache file creation bug
|
|
||||||
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
|
||||||
.idea/$CACHE_FILE$
|
|
||||||
|
|
||||||
# CodeStream plugin
|
|
||||||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
|
||||||
.idea/codestream.xml
|
|
||||||
|
|
||||||
# Azure Toolkit for IntelliJ plugin
|
|
||||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
|
||||||
.idea/**/azureSettings.xml
|
|
||||||
|
|
||||||
### Windows ###
|
### Windows ###
|
||||||
# Windows thumbnail cache files
|
# Windows thumbnail cache files
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
@@ -371,4 +278,4 @@ $RECYCLE.BIN/
|
|||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
# End of https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||||
|
|
||||||
/build
|
/build
|
||||||
|
|||||||
5
.idea/runConfigurations/Run_Dev_Fields.xml
generated
Normal file
5
.idea/runConfigurations/Run_Dev_Fields.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run Dev Fields" type="NodeJSConfigurationType" application-parameters="fields" path-to-js-file="node_modules/.pnpm/nodemon@3.0.1/node_modules/nodemon/bin/nodemon.js" working-dir="$PROJECT_DIR$">
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
5
.idea/runConfigurations/Run_Dev__community.xml
generated
Normal file
5
.idea/runConfigurations/Run_Dev__community.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run Dev _community" type="NodeJSConfigurationType" application-parameters="_community" path-to-js-file="node_modules/.pnpm/nodemon@3.0.1/node_modules/nodemon/bin/nodemon.js" working-dir="$PROJECT_DIR$">
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
123
CHANGELOG.md
123
CHANGELOG.md
@@ -1,3 +1,61 @@
|
|||||||
|
## [2.7.0](https://github.com/payloadcms/payload/compare/v2.6.0...v2.7.0) (2024-01-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **db-mongodb:** improve transaction support by passing req to migrations ([682eca2](https://github.com/payloadcms/payload/commit/682eca21860a4e2b2ab0bfd85613818790247224))
|
||||||
|
* **db-postgres:** improve transaction support by passing req to migrations ([555d027](https://github.com/payloadcms/payload/commit/555d02769a8731aeebbff9b67f9b0e1022904ade))
|
||||||
|
* hasMany property for text fields ([#4605](https://github.com/payloadcms/payload/issues/4605)) ([f43cf18](https://github.com/payloadcms/payload/commit/f43cf185d45b3c75fa0d78acd91e6cb60d87f166))
|
||||||
|
* improve transaction support by passing req to migrations ([1d14d9f](https://github.com/payloadcms/payload/commit/1d14d9f8b8ed077691175030182f094bb300ed17))
|
||||||
|
* **plugin-seo:** add i18n ([#4665](https://github.com/payloadcms/payload/issues/4665)) ([3027a03](https://github.com/payloadcms/payload/commit/3027a03ad11ecd679278e44a013e4dea4aa42b8d))
|
||||||
|
* provide document info to ActionsProvider ([#4696](https://github.com/payloadcms/payload/issues/4696)) ([6a8a6e4](https://github.com/payloadcms/payload/commit/6a8a6e4ef4913e0889e4d2eac82b28b9e4e8db22))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* adds objectID validation to isValidID if of type `text` ([#4689](https://github.com/payloadcms/payload/issues/4689)) ([d419275](https://github.com/payloadcms/payload/commit/d419275fb50f0922307f2d3b4c0fcf80ac5ec98b))
|
||||||
|
* allow json field to be saved empty and reflect value changes ([#4687](https://github.com/payloadcms/payload/issues/4687)) ([0fb3a9c](https://github.com/payloadcms/payload/commit/0fb3a9ca89d1b63faea179bfa9b5b3d0a69c9398))
|
||||||
|
* custom ids in versions ([#4680](https://github.com/payloadcms/payload/issues/4680)) ([5d15955](https://github.com/payloadcms/payload/commit/5d15955f839d3f0cc557d8a8d7cc3a9e52e2f6b1))
|
||||||
|
* custom overrides of breadcrumb and parent fields ([7db58b4](https://github.com/payloadcms/payload/commit/7db58b482bba7e715c5be23cfe1a84295e95da29))
|
||||||
|
* **db-mongodb:** migration error calling beginTransaction with transactionOptions false ([21b9453](https://github.com/payloadcms/payload/commit/21b9453cf4e6eebf145d89a0190942015658413d))
|
||||||
|
* **db-mongodb:** querying plan for collections ignoring indexes ([#4655](https://github.com/payloadcms/payload/issues/4655)) ([63bc4ca](https://github.com/payloadcms/payload/commit/63bc4cabe1dea5f233aa1d9d4e64f3af93a8e081))
|
||||||
|
* **db-postgres:** incorrect results querying json field using exists operator ([9d9ac0e](https://github.com/payloadcms/payload/commit/9d9ac0ec28c97281bfdc7d6fb78c52baea492380))
|
||||||
|
* **db-postgres:** migrate down only runs latest batch size ([6acfae8](https://github.com/payloadcms/payload/commit/6acfae8ee7614746797e1fa91e1fd41c0240fdcd))
|
||||||
|
* **db-postgres:** query on json properties ([ec4d2f9](https://github.com/payloadcms/payload/commit/ec4d2f97cbf1c89d837372059bf3bb77f3ea6594))
|
||||||
|
* **db-postgres:** validation prevents group fields in blocks ([#4699](https://github.com/payloadcms/payload/issues/4699)) ([cab6bab](https://github.com/payloadcms/payload/commit/cab6babd608daeaabf9b63b1b446fded6804b60f))
|
||||||
|
* non-boolean condition result causes infinite looping ([#4579](https://github.com/payloadcms/payload/issues/4579)) ([a3e7816](https://github.com/payloadcms/payload/commit/a3e78161b551e8286063a173645a1d3dee162ad1))
|
||||||
|
* **plugin-form-builder:** slate serializer should replace curly braces in links ([#4703](https://github.com/payloadcms/payload/issues/4703)) ([28a3012](https://github.com/payloadcms/payload/commit/28a30120dd1aa3279fb2133aa0a0b1638d144be4))
|
||||||
|
* **plugin-nested-docs:** breadcrumbsFieldSlug used in resaveSelfAfterCreate hook ([a5a91c0](https://github.com/payloadcms/payload/commit/a5a91c08a9ade1482c512d3fa4c4f519ad85cf74))
|
||||||
|
* **plugin-nested-docs:** children wrongly publishing draft data ([#4692](https://github.com/payloadcms/payload/issues/4692)) ([5539942](https://github.com/payloadcms/payload/commit/55399424a13b1e0532d9eeefd09d442c107c3eda))
|
||||||
|
* **plugin-nested-docs:** custom parent field slug ([635e7c2](https://github.com/payloadcms/payload/commit/635e7c26e8b3b5138cf5a9bcb29e8ddd4b1e69b6))
|
||||||
|
* **plugin-nested-docs:** parent filterOptions errors when specifying breadcrumbsFieldSlug ([c4a4678](https://github.com/payloadcms/payload/commit/c4a4678afb097cf94c682595a78e416767a1fea8))
|
||||||
|
* prevents row overflow ([#4704](https://github.com/payloadcms/payload/issues/4704)) ([9828772](https://github.com/payloadcms/payload/commit/98287728900cb88fa6a465899f030f81df28fc69))
|
||||||
|
* relations with number based ids (postgres) show untitled ID: x ([1b91408](https://github.com/payloadcms/payload/commit/1b914083c8ee0c1b1d64fa7d4471ede0a24cfdb7))
|
||||||
|
* sidebar fields not disabled by access permissions ([#4682](https://github.com/payloadcms/payload/issues/4682)) ([85e38b7](https://github.com/payloadcms/payload/commit/85e38b7cfd5c0772344c4a8fb5100f7c48eb508f))
|
||||||
|
* unlock user condition always passes due to seconds conversion ([#4610](https://github.com/payloadcms/payload/issues/4610)) ([d543665](https://github.com/payloadcms/payload/commit/d543665995410256f77fe136173339aee6dcc7da))
|
||||||
|
|
||||||
|
## [2.6.0](https://github.com/payloadcms/payload/compare/v2.5.0...v2.6.0) (2024-01-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **db-mongodb:** add transactionOptions ([f2c8ac4](https://github.com/payloadcms/payload/commit/f2c8ac4a9aa9120339af6759170f5a708469698d))
|
||||||
|
* extend locales to have fallbackLocales ([9fac2ef](https://github.com/payloadcms/payload/commit/9fac2ef24e2ade4cf55b0d6a0e7f67e0edf57539))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* "The punycode module is deprecated" warning by updating nodemailer ([00d8480](https://github.com/payloadcms/payload/commit/00d8480062d99dee56ef61a955f48a92efa6cbea))
|
||||||
|
* adjusts json field joi schema to allow editorOptions ([bff4cf5](https://github.com/payloadcms/payload/commit/bff4cf518f748efb9179f112c606d11d25db3d99))
|
||||||
|
* **db-postgres:** Wait for transaction to complete on commit ([#4582](https://github.com/payloadcms/payload/issues/4582)) ([a71d37b](https://github.com/payloadcms/payload/commit/a71d37b39806cd5956378a10246802d01d06c2dd))
|
||||||
|
* detect language from request headers accept-language ([#4656](https://github.com/payloadcms/payload/issues/4656)) ([69a9944](https://github.com/payloadcms/payload/commit/69a99445c9f1638a962a9c08ffe0bdc22e538bf6))
|
||||||
|
* graphql multiple locales ([98890ee](https://github.com/payloadcms/payload/commit/98890eee1f527c8f245b2353d7e1caca4d2a7d8c))
|
||||||
|
* navigation locks when modal is closed with esc ([#4664](https://github.com/payloadcms/payload/issues/4664)) ([be3beab](https://github.com/payloadcms/payload/commit/be3beabb9bafa137aa89e84cf47246017e969be8))
|
||||||
|
* req.locale and req.fallbackLocale get reassigned in local operations ([aa048d5](https://github.com/payloadcms/payload/commit/aa048d5409acd42b8f56367a16934085df9fbce2))
|
||||||
|
* resets actions array when navigating out of view with actions ([#4585](https://github.com/payloadcms/payload/issues/4585)) ([5c55231](https://github.com/payloadcms/payload/commit/5c5523195ccfa94a9bf42441e2a378f87836e64d))
|
||||||
|
* **richtext-lexical:** z-index issues ([#4570](https://github.com/payloadcms/payload/issues/4570)) ([8015e99](https://github.com/payloadcms/payload/commit/8015e999cd5834f532a200ef03fd392d04b3209f))
|
||||||
|
* tab field error when using the same interface name ([#4657](https://github.com/payloadcms/payload/issues/4657)) ([f1fa374](https://github.com/payloadcms/payload/commit/f1fa374ed12b50fdf210f17ae1dda603f09a9726))
|
||||||
|
|
||||||
## [2.5.0](https://github.com/payloadcms/payload/compare/v2.4.0...v2.5.0) (2023-12-19)
|
## [2.5.0](https://github.com/payloadcms/payload/compare/v2.4.0...v2.5.0) (2023-12-19)
|
||||||
|
|
||||||
|
|
||||||
@@ -61,10 +119,67 @@
|
|||||||
|
|
||||||
#### @payloadcms/richtext-lexical
|
#### @payloadcms/richtext-lexical
|
||||||
|
|
||||||
* **richtext-lexical:** rename TreeviewFeature into TreeViewFeature (#4520)
|
* **richtext-lexical:** rename TreeviewFeature into TreeViewFeature ([#4520](https://github.com/payloadcms/payload/issues/4520)) ([c49fd66](https://github.com/payloadcms/payload/commit/c49fd6692231b68ca61b079103a0fd7aa4673be1))
|
||||||
* **richtext-lexical:** link node: change doc data format to be consistent with relationship field (#4504)
|
|
||||||
* **richtext-lexical:** improve floating select menu Dropdown classNames (#4444)
|
If you import TreeviewFeature, you have to rename the import to use TreeViewFeature (capitalized "V")
|
||||||
* **richtext-lexical:** lazy import React components to prevent client-only code from leaking into the server (#4290)
|
|
||||||
|
* **richtext-lexical:** link node: change doc data format to be consistent with relationship field ([#4504](https://github.com/payloadcms/payload/issues/4504)) ([cc0ba89](https://github.com/payloadcms/payload/commit/cc0ba895188f40181c6ba3779f66d547d4ea66f9))
|
||||||
|
|
||||||
|
An unpopulated, internal link node no longer saves the doc id under fields.doc.value.id. Now, it saves it under fields.doc.value.
|
||||||
|
Migration inside of payload is automatic. If you are reading from the link node inside of your frontend though, you will have to adjust it.
|
||||||
|
|
||||||
|
* **richtext-lexical:** improve floating select menu Dropdown classNames ([#4444](https://github.com/payloadcms/payload/issues/4444)) ([9331204](https://github.com/payloadcms/payload/commit/9331204295bfeaf7dd10bc075f42995b2cab2de4))
|
||||||
|
|
||||||
|
Dropdown component has a new mandatory sectionKey prop
|
||||||
|
|
||||||
|
* **richtext-lexical:** lazy import React components to prevent client-only code from leaking into the server ([#4290](https://github.com/payloadcms/payload/issues/4290)) ([5de347f](https://github.com/payloadcms/payload/commit/5de347ffffca3bf38315d3d87d2ccc5c28cd2723))
|
||||||
|
|
||||||
|
1. Most important: If you are updating `@payloadcms/richtext-lexical` to v0.4.0 or higher, you will HAVE to update payload to the latest version as well. If you don't update it, payload likely won't start up due to validation errors. It's generally good practice to upgrade packages prefixed with @payloadcms/ together with payload and keep the versions in sync.
|
||||||
|
|
||||||
|
2. `@payloadcms/richtext-slate` is not affected by this.
|
||||||
|
|
||||||
|
3. Every single property in the `Feature` interface which accepts a React component now no longer accepts a React component, but a function which imports a React component instead. This is done to ensure no unnecessary client-only code is leaked to the server when importing Features on a server.
|
||||||
|
Here's an example migration:
|
||||||
|
|
||||||
|
Old:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { BlockIcon } from '../../lexical/ui/icons/Block'
|
||||||
|
...
|
||||||
|
Icon: BlockIcon,
|
||||||
|
```
|
||||||
|
|
||||||
|
New:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// import { BlockIcon } from '../../lexical/ui/icons/Block' // <= Remove this import
|
||||||
|
...
|
||||||
|
Icon: () =>
|
||||||
|
// @ts-expect-error
|
||||||
|
import('../../lexical/ui/icons/Block').then((module) => module.BlockIcon),
|
||||||
|
```
|
||||||
|
|
||||||
|
Or alternatively, if you're using default exports instead of named exports:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// import BlockIcon from '../../lexical/ui/icons/Block' // <= Remove this import
|
||||||
|
...
|
||||||
|
Icon: () =>
|
||||||
|
// @ts-expect-error
|
||||||
|
import('../../lexical/ui/icons/Block'),
|
||||||
|
```
|
||||||
|
|
||||||
|
4. The types for `SanitizedEditorConfig` and `EditorConfig` have changed. Their respective `lexical` property no longer expects the `LexicalEditorConfig`. It now expects a function returning the `LexicalEditorConfig`. You will have to adjust this if you adjusted that property anywhere, e.g. when initializing the lexical field editor property, or when initializing a new headless editor.
|
||||||
|
|
||||||
|
5. The following exports are now exported from the `@payloadcms/richtext-lexical/components` subpath exports instead of `@payloadcms/richtext-lexical`:
|
||||||
|
|
||||||
|
- `ToolbarButton`
|
||||||
|
- `ToolbarDropdown`
|
||||||
|
- `RichTextCell`
|
||||||
|
- `RichTextField`
|
||||||
|
- `defaultEditorLexicalConfig`
|
||||||
|
|
||||||
|
You will have to adjust your imports, only if you import any of those properties in your project.
|
||||||
|
|
||||||
## @payloadcms/richtext-*
|
## @payloadcms/richtext-*
|
||||||
|
|
||||||
|
|||||||
@@ -534,14 +534,13 @@ When swapping out the `Field` component, you'll be responsible for sending and r
|
|||||||
```tsx
|
```tsx
|
||||||
import { useField } from 'payload/components/forms'
|
import { useField } from 'payload/components/forms'
|
||||||
|
|
||||||
type Props = { path: string }
|
const CustomTextField: React.FC<{ path: string }> = ({ path }) => {
|
||||||
|
|
||||||
const CustomTextField: React.FC<Props> = ({ path }) => {
|
|
||||||
// highlight-start
|
// highlight-start
|
||||||
const { value, setValue } = useField<Props>({ path })
|
const { value, setValue } = useField<string>({ path })
|
||||||
// highlight-end
|
// highlight-end
|
||||||
|
|
||||||
return <input onChange={(e) => setValue(e.target.value)} value={value.path} />
|
|
||||||
|
return <input onChange={(e) => setValue(e.target.value)} value={value} />
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,26 @@ Payload Cloud gives you S3 file storage backed by Cloudflare as a CDN, and this
|
|||||||
|
|
||||||
AWS Cognito is used for authentication to your S3 bucket. The [Payload Cloud Plugin](https://github.com/payloadcms/plugin-cloud) will automatically pick up these values. These values are only if you'd like to access your files directly, outside of Payload Cloud.
|
AWS Cognito is used for authentication to your S3 bucket. The [Payload Cloud Plugin](https://github.com/payloadcms/plugin-cloud) will automatically pick up these values. These values are only if you'd like to access your files directly, outside of Payload Cloud.
|
||||||
|
|
||||||
|
#### Accessing Files Outside of Payload Cloud
|
||||||
|
|
||||||
|
If you'd like to access your files outside of Payload Cloud, you'll need to retrieve some values from your project's settings and put them into your environment variables. In Payload Cloud, navigate to the File Storage tab and copy the values using the copy button. Put these values in your .env file. Also copy the Cognito Password value separately and put into your .env file as well.
|
||||||
|
|
||||||
|
When you are done, you should have the following values in your .env file:
|
||||||
|
|
||||||
|
```env
|
||||||
|
PAYLOAD_CLOUD=true
|
||||||
|
PAYLOAD_CLOUD_ENVIRONMENT=prod
|
||||||
|
PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID=
|
||||||
|
PAYLOAD_CLOUD_COGNITO_USER_POOL_ID=
|
||||||
|
PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID=
|
||||||
|
PAYLOAD_CLOUD_PROJECT_ID=
|
||||||
|
PAYLOAD_CLOUD_BUCKET=
|
||||||
|
PAYLOAD_CLOUD_BUCKET_REGION=
|
||||||
|
PAYLOAD_CLOUD_COGNITO_PASSWORD=
|
||||||
|
```
|
||||||
|
|
||||||
|
The plugin will pick up these values and use them to access your files.
|
||||||
|
|
||||||
### Build Settings
|
### Build Settings
|
||||||
|
|
||||||
You can update settings from your Project’s Settings tab. Changes to your build settings will trigger a redeployment of your project.
|
You can update settings from your Project’s Settings tab. Changes to your build settings will trigger a redeployment of your project.
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ desc: Add and maintain as many locales as you need by adding Localization to you
|
|||||||
keywords: localization, internationalization, i18n, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
keywords: localization, internationalization, i18n, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||||
---
|
---
|
||||||
|
|
||||||
Payload features deep field-based localization support. Maintaining as many locales as you need is easy. All localization support is opt-in by default. To do so, follow the two steps below.
|
Payload features deep field-based localization support. Maintaining as many locales as you need is easy. All
|
||||||
|
localization support is opt-in by default. To do so, follow the two steps below.
|
||||||
|
|
||||||
### Enabling in the Payload config
|
### Enabling in the Payload config
|
||||||
|
|
||||||
Add the `localization` property to your Payload config to enable localization project-wide. You'll need to provide a list of all locales that you'd like to support as well as set a few other options.
|
Add the `localization` property to your Payload config to enable localization project-wide. You'll need to provide a
|
||||||
|
list of all locales that you'd like to support as well as set a few other options.
|
||||||
|
|
||||||
**Example Payload config set up for localization:**
|
**Example Payload config set up for localization:**
|
||||||
|
|
||||||
@@ -57,7 +59,8 @@ export default buildConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
**Example Payload config set up for localization with full locales objects (including [internationalization](/docs/configuration/i18n) support):**
|
**Example Payload config set up for localization with full locales objects (
|
||||||
|
including [internationalization](/docs/configuration/i18n) support):**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { buildConfig } from 'payload/config'
|
import { buildConfig } from 'payload/config'
|
||||||
@@ -93,35 +96,60 @@ export default buildConfig({
|
|||||||
|
|
||||||
**`locales`**
|
**`locales`**
|
||||||
|
|
||||||
Array-based list of all locales that you would like to support. These can be strings of locale codes or objects with a `label`, a locale `code`, and the `rtl` (right-to-left) property. The locale codes do not need to be in any specific format. It's up to you to define how to represent your locales. Common patterns are to use two-letter ISO 639 language codes or four-letter language and country codes (ISO 3166‑1) such as `en-US`, `en-UK`, `es-MX`, etc.
|
Array-based list of all the languages that you would like to support. This can be an array containing strings for each
|
||||||
|
language code you want your project to store and serve or objects with a `label`, a locale `code`, `rtl` (
|
||||||
|
right-to-left), and `fallbackLocale` property. The locale codes do not need to be in any specific format. It's up to you
|
||||||
|
to define how to represent your locales. Common patterns are to use two-letter ISO 639 language codes or four-letter
|
||||||
|
language and country codes (ISO 3166‑1) such as `en-US`, `en-UK`, `es-MX`, etc.
|
||||||
|
|
||||||
|
### Locale Properties:
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|----------------------|--------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| **`code`** \* | Unique code to identify the language throughout the APIs for `locale` and `fallbackLocale` |
|
||||||
|
| **`label`** | A string to use for the selector when choosing a language, or an object keyed on the i18n keys for different languages in use. |
|
||||||
|
| **`rtl`** | A boolean that when true will make the admin UI display in Right-To-Left. |
|
||||||
|
| **`fallbackLocale`** | The code for this language to fallback to when properties of a document are not present. |
|
||||||
|
|
||||||
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
**`defaultLocale`**
|
**`defaultLocale`**
|
||||||
|
|
||||||
Required string that matches one of the locale codes from the array provided. By default, if no locale is specified, documents will be returned in this locale.
|
Required string that matches one of the locale codes from the array provided. By default, if no locale is specified,
|
||||||
|
documents will be returned in this locale.
|
||||||
|
|
||||||
**`fallback`**
|
**`fallback`**
|
||||||
|
|
||||||
Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a localized value corresponding to the requested locale, then if this property is enabled, the document will automatically fall back to the fallback locale value. If this property is not enabled, the value will not be populated.
|
Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a
|
||||||
|
localized value corresponding to the requested locale, then if this property is enabled, the document will automatically
|
||||||
|
fall back to the fallback locale value. If this property is not enabled, the value will not be populated.
|
||||||
|
|
||||||
### Field by field localization
|
### Field by field localization
|
||||||
|
|
||||||
Payload localization works on a **field** level—not a document level. In addition to configuring the base Payload config to support localization, you need to specify each field that you would like to localize.
|
Payload localization works on a **field** level—not a document level. In addition to configuring the base Payload config
|
||||||
|
to support localization, you need to specify each field that you would like to localize.
|
||||||
|
|
||||||
**Here is an example of how to enable localization for a field:**
|
**Here is an example of how to enable localization for a field:**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
type: 'text',
|
type
|
||||||
// highlight-start
|
:
|
||||||
localized: true,
|
'text',
|
||||||
|
// highlight-start
|
||||||
|
localized
|
||||||
|
:
|
||||||
|
true,
|
||||||
// highlight-end
|
// highlight-end
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
With the above configuration, the `title` field will now be saved in the database as an object of all locales instead of a single string.
|
With the above configuration, the `title` field will now be saved in the database as an object of all locales instead of
|
||||||
|
a single string.
|
||||||
|
|
||||||
All field types with a `name` property support the `localized` property—even the more complex field types like `array`s and `block`s.
|
All field types with a `name` property support the `localized` property—even the more complex field types like `array`s
|
||||||
|
and `block`s.
|
||||||
|
|
||||||
<Banner>
|
<Banner>
|
||||||
<strong>Note:</strong>
|
<strong>Note:</strong>
|
||||||
@@ -143,7 +171,8 @@ All field types with a `name` property support the `localized` property—even t
|
|||||||
|
|
||||||
### Retrieving localized docs
|
### Retrieving localized docs
|
||||||
|
|
||||||
When retrieving documents, you can specify which locale you'd like to receive as well as which fallback locale should be used.
|
When retrieving documents, you can specify which locale you'd like to receive as well as which fallback locale should be
|
||||||
|
used.
|
||||||
|
|
||||||
##### REST API
|
##### REST API
|
||||||
|
|
||||||
@@ -155,7 +184,8 @@ Specify your desired locale by providing the `locale` query parameter directly i
|
|||||||
|
|
||||||
**`?fallback-locale=`**
|
**`?fallback-locale=`**
|
||||||
|
|
||||||
Specify fallback locale to be used by providing the `fallback-locale` query parameter. This can be provided as either a valid locale as provided to your base Payload config, or `'null'`, `'false'`, or `'none'` to disable falling back.
|
Specify fallback locale to be used by providing the `fallback-locale` query parameter. This can be provided as either a
|
||||||
|
valid locale as provided to your base Payload config, or `'null'`, `'false'`, or `'none'` to disable falling back.
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
@@ -167,7 +197,9 @@ fetch('https://localhost:3000/api/pages?locale=es&fallback-locale=none');
|
|||||||
|
|
||||||
In the GraphQL API, you can specify `locale` and `fallbackLocale` args to all relevant queries and mutations.
|
In the GraphQL API, you can specify `locale` and `fallbackLocale` args to all relevant queries and mutations.
|
||||||
|
|
||||||
The `locale` arg will only accept valid locales, but locales will be formatted automatically as valid GraphQL enum values (dashes or special characters will be converted to underscores, spaces will be removed, etc.). If you are curious to see how locales are auto-formatted, you can use the [GraphQL playground](/docs/graphql/overview#graphql-playground).
|
The `locale` arg will only accept valid locales, but locales will be formatted automatically as valid GraphQL enum
|
||||||
|
values (dashes or special characters will be converted to underscores, spaces will be removed, etc.). If you are curious
|
||||||
|
to see how locales are auto-formatted, you can use the [GraphQL playground](/docs/graphql/overview#graphql-playground).
|
||||||
|
|
||||||
The `fallbackLocale` arg will accept valid locales as well as `none` to disable falling back.
|
The `fallbackLocale` arg will accept valid locales as well as `none` to disable falling back.
|
||||||
|
|
||||||
@@ -175,11 +207,11 @@ The `fallbackLocale` arg will accept valid locales as well as `none` to disable
|
|||||||
|
|
||||||
```graphql
|
```graphql
|
||||||
query {
|
query {
|
||||||
Posts(locale: de, fallbackLocale: none) {
|
Posts(locale: de, fallbackLocale: none) {
|
||||||
docs {
|
docs {
|
||||||
title
|
title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -191,7 +223,9 @@ query {
|
|||||||
|
|
||||||
##### Local API
|
##### Local API
|
||||||
|
|
||||||
You can specify `locale` as well as `fallbackLocale` within the Local API as well as properties on the `options` argument. The `locale` property will accept any valid locale, and the `fallbackLocale` property will accept any valid locale as well as `'null'`, `'false'`, `false`, and `'none'`.
|
You can specify `locale` as well as `fallbackLocale` within the Local API as well as properties on the `options`
|
||||||
|
argument. The `locale` property will accept any valid locale, and the `fallbackLocale` property will accept any valid
|
||||||
|
locale as well as `'null'`, `'false'`, `false`, and `'none'`.
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ keywords: database, migrations, ddl, sql, mongodb, postgres, documentation, Cont
|
|||||||
desc: Payload features first-party database migrations all done in TypeScript.
|
desc: Payload features first-party database migrations all done in TypeScript.
|
||||||
---
|
---
|
||||||
|
|
||||||
Payload exposes a full suite of migration controls available for your use. Migration commands are accessible via the `npm run payload` command in your project directory.
|
Payload exposes a full suite of migration controls available for your use. Migration commands are accessible via
|
||||||
|
the `npm run payload` command in your project directory.
|
||||||
|
|
||||||
Ensure you have an npm script called "payload" in your `package.json` file.
|
Ensure you have an npm script called "payload" in your `package.json` file.
|
||||||
|
|
||||||
@@ -24,33 +25,42 @@ Ensure you have an npm script called "payload" in your `package.json` file.
|
|||||||
|
|
||||||
### Migration file contents
|
### Migration file contents
|
||||||
|
|
||||||
Payload stores all created migrations in a folder that you can specify. By default, migrations are stored in `./src/migrations`.
|
Payload stores all created migrations in a folder that you can specify. By default, migrations are stored
|
||||||
|
in `./src/migrations`.
|
||||||
|
|
||||||
A migration file has two exports - an `up` function, which is called when a migration is executed, and a `down` function that will be called if for some reason the migration fails to complete successfully. The `up` function should contain all changes that you attempt to make within the migration, and the `down` should ideally revert any changes you make.
|
A migration file has two exports - an `up` function, which is called when a migration is executed, and a `down` function
|
||||||
|
that will be called if for some reason the migration fails to complete successfully. The `up` function should contain
|
||||||
|
all changes that you attempt to make within the migration, and the `down` should ideally revert any changes you make.
|
||||||
|
|
||||||
For an added level of safety, migrations should leverage Payload [transactions](/docs/database/transactions).
|
For an added level of safety, migrations should leverage Payload [transactions](/docs/database/transactions). Migration
|
||||||
|
functions should make use of the `req` by adding it to the arguments of your payload local API calls such
|
||||||
|
as `payload.create` and database adapter methods like `payload.db.create`.
|
||||||
|
|
||||||
Here is an example migration file:
|
Here is an example migration file:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'
|
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'
|
||||||
|
|
||||||
export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
export async function up ({ payload, req }: MigrateUpArgs): Promise<void> {
|
||||||
// Perform changes to your database here.
|
// Perform changes to your database here.
|
||||||
// You have access to `payload` as an argument, and
|
// You have access to `payload` as an argument, and
|
||||||
// everything is done in TypeScript.
|
// everything is done in TypeScript.
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function down({ payload }: MigrateDownArgs): Promise<void> {
|
export async function down ({ payload, req }: MigrateDownArgs): Promise<void> {
|
||||||
// Do whatever you need to revert changes if the `up` function fails
|
// Do whatever you need to revert changes if the `up` function fails
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Migrations Directory
|
### Migrations Directory
|
||||||
|
|
||||||
Each DB adapter has an optional property `migrationDir` where you can override where you want your migrations to be stored/read. If this is not specified, Payload will check the default and possibly make a best effort to find your migrations directory by searching in common locations ie. `./src/migrations`, `./dist/migrations`, `./migrations`, etc.
|
Each DB adapter has an optional property `migrationDir` where you can override where you want your migrations to be
|
||||||
|
stored/read. If this is not specified, Payload will check the default and possibly make a best effort to find your
|
||||||
|
migrations directory by searching in common locations ie. `./src/migrations`, `./dist/migrations`, `./migrations`, etc.
|
||||||
|
|
||||||
All database adapters should implement similar migration patterns, but there will be small differences based on the adapter and its specific needs. Below is a list of all migration commands that should be supported by your database adapter.
|
All database adapters should implement similar migration patterns, but there will be small differences based on the
|
||||||
|
adapter and its specific needs. Below is a list of all migration commands that should be supported by your database
|
||||||
|
adapter.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
@@ -64,7 +74,8 @@ npm run payload migrate
|
|||||||
|
|
||||||
### Create
|
### Create
|
||||||
|
|
||||||
Create a new migration file in the migrations directory. You can optionally name the migration that will be created. By default, migrations will be named using a timestamp.
|
Create a new migration file in the migrations directory. You can optionally name the migration that will be created. By
|
||||||
|
default, migrations will be named using a timestamp.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
npm run payload migrate:create optional-name-here
|
npm run payload migrate:create optional-name-here
|
||||||
@@ -72,7 +83,8 @@ npm run payload migrate:create optional-name-here
|
|||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
The `migrate:status` command will check the status of migrations and output a table of which migrations have been run, and which migrations have not yet run.
|
The `migrate:status` command will check the status of migrations and output a table of which migrations have been run,
|
||||||
|
and which migrations have not yet run.
|
||||||
|
|
||||||
`payload migrate:status`
|
`payload migrate:status`
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ desc: Payload has supported MongoDB natively since we started. The flexible natu
|
|||||||
keywords: MongoDB, documentation, typescript, Content Management System, cms, headless, javascript, node, react, express
|
keywords: MongoDB, documentation, typescript, Content Management System, cms, headless, javascript, node, react, express
|
||||||
---
|
---
|
||||||
|
|
||||||
To use Payload with MongoDB, install the package `@payloadcms/db-mongodb`. It will come with everything you need to store your Payload data in MongoDB.
|
To use Payload with MongoDB, install the package `@payloadcms/db-mongodb`. It will come with everything you need to
|
||||||
|
store your Payload data in MongoDB.
|
||||||
|
|
||||||
Then from there, pass it to your Payload config as follows:
|
Then from there, pass it to your Payload config as follows:
|
||||||
|
|
||||||
@@ -29,16 +30,18 @@ export default buildConfig({
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
|
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
|
||||||
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
|
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
|
||||||
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
|
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
|
||||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||||
|
| `transactionOptions` | An object with configuration properties used in [transactions](https://www.mongodb.com/docs/manual/core/transactions/) or `false` which will disable the use of transactions. | |
|
||||||
|
|
||||||
### Access to Mongoose models
|
### Access to Mongoose models
|
||||||
|
|
||||||
After Payload is initialized, this adapter exposes all of your Mongoose models and they are available for you to work with directly.
|
After Payload is initialized, this adapter exposes all of your Mongoose models and they are available for you to work
|
||||||
|
with directly.
|
||||||
|
|
||||||
You can access Mongoose models as follows:
|
You can access Mongoose models as follows:
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const afterChange: CollectionAfterChangeHook = async ({ req }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Should this call fail, it will not rollback other changes
|
// Should this call fail, it will not rollback other changes
|
||||||
// because the req (and its transationID) is not passed through
|
// because the req (and its transactionID) is not passed through
|
||||||
const safelyIgnoredAsync = req.payload.create({
|
const safelyIgnoredAsync = req.payload.create({
|
||||||
collection: 'my-slug',
|
collection: 'my-slug',
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ import * as React from 'react';
|
|||||||
import { SelectInput, useField } from 'payload/components/forms';
|
import { SelectInput, useField } from 'payload/components/forms';
|
||||||
import { useAuth } from 'payload/components/utilities';
|
import { useAuth } from 'payload/components/utilities';
|
||||||
|
|
||||||
type customSelectProps = {
|
type CustomSelectProps = {
|
||||||
path: string;
|
path: string;
|
||||||
options: {
|
options: {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -164,7 +164,7 @@ export const CustomSelectComponent: React.FC<CustomSelectProps> = ({ path, optio
|
|||||||
name={path}
|
name={path}
|
||||||
options={adjustedOptions}
|
options={adjustedOptions}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={() => setValue(e.value)}
|
onChange={(e) => setValue(e.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ keywords: text, fields, config, configuration, documentation, Content Management
|
|||||||
| **`required`** | Require this field to have a value. |
|
| **`required`** | Require this field to have a value. |
|
||||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
|
| **`hasMany`** | Makes this field an ordered array of text instead of just a single text. |
|
||||||
|
| **`minRows`** | Minimum number of texts in the array, if `hasMany` is set to true. |
|
||||||
|
| **`maxRows`** | Maximum number of texts in the array, if `hasMany` is set to true. |
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
### Admin config
|
### Admin config
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ const result = await payload.unlock({
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Returned result will be a boolean representing success or failure
|
// Returned result will be a boolean representing success or failure
|
||||||
const result = await payload.verify({
|
const result = await payload.verifyEmail({
|
||||||
collection: 'users', // required
|
collection: 'users', // required
|
||||||
token: 'afh3o2jf2p3f...', // the token saved on the user as `_verificationToken`
|
token: 'afh3o2jf2p3f...', // the token saved on the user as `_verificationToken`
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,11 +8,21 @@ keywords: plugins, nested, documents, parent, child, sibling, relationship
|
|||||||
|
|
||||||
[](https://www.npmjs.com/package/@payloadcms/plugin-nested-docs)
|
[](https://www.npmjs.com/package/@payloadcms/plugin-nested-docs)
|
||||||
|
|
||||||
This plugin allows you to easily nest the documents of your application inside of one another. It does so by adding a new `parent` field onto each of your documents that, when selected, attaches itself to the parent's tree. When you edit the great-great-grandparent of a document, for instance, all of its descendants are recursively updated. This is an extremely powerful way of achieving hierarchy within a collection, such as parent/child relationship between pages.
|
This plugin allows you to easily nest the documents of your application inside of one another. It does so by adding a
|
||||||
|
new `parent` field onto each of your documents that, when selected, attaches itself to the parent's tree. When you edit
|
||||||
|
the great-great-grandparent of a document, for instance, all of its descendants are recursively updated. This is an
|
||||||
|
extremely powerful way of achieving hierarchy within a collection, such as parent/child relationship between pages.
|
||||||
|
|
||||||
Documents also receive a new `breadcrumbs` field. Once a parent is assigned, these breadcrumbs are populated based on each ancestor up the tree. Breadcrumbs allow you to dynamically generate labels and URLs based on the document's position in the hierarchy. Even if the slug of a parent document changes, or the entire tree is nested another level deep, changes will cascade down the entire tree and all breadcrumbs will reflect those changes.
|
Documents also receive a new `breadcrumbs` field. Once a parent is assigned, these breadcrumbs are populated based on
|
||||||
|
each ancestor up the tree. Breadcrumbs allow you to dynamically generate labels and URLs based on the document's
|
||||||
|
position in the hierarchy. Even if the slug of a parent document changes, or the entire tree is nested another level
|
||||||
|
deep, changes will cascade down the entire tree and all breadcrumbs will reflect those changes.
|
||||||
|
|
||||||
With this pattern you can perform whatever side-effects your applications needs on even the most deeply nested documents. For example, you could easily add a custom `fullTitle` field onto each document and inject the parent's title onto it, such as "Parent Title > Child Title". This would allow you to then perform searches and filters based on _that_ field instead of the original title. This is especially useful if you happen to have two documents with identical titles but different parents.
|
With this pattern you can perform whatever side-effects your applications needs on even the most deeply nested
|
||||||
|
documents. For example, you could easily add a custom `fullTitle` field onto each document and inject the parent's title
|
||||||
|
onto it, such as "Parent Title > Child Title". This would allow you to then perform searches and filters based on _that_
|
||||||
|
field instead of the original title. This is especially useful if you happen to have two documents with identical titles
|
||||||
|
but different parents.
|
||||||
|
|
||||||
<Banner type="info">
|
<Banner type="info">
|
||||||
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-nested-docs). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20nested-docs&template=bug_report.md&title=plugin-nested-docs%3A) with as much detail as possible.
|
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-nested-docs). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20nested-docs&template=bug_report.md&title=plugin-nested-docs%3A) with as much detail as possible.
|
||||||
@@ -29,7 +39,8 @@ With this pattern you can perform whatever side-effects your applications needs
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
|
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com),
|
||||||
|
or [PNPM](https://pnpm.io):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn add @payloadcms/plugin-nested-docs
|
yarn add @payloadcms/plugin-nested-docs
|
||||||
@@ -37,7 +48,8 @@ Install the plugin using any JavaScript package manager like [Yarn](https://yarn
|
|||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
|
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin
|
||||||
|
with [options](#options):
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { buildConfig } from 'payload/config'
|
import { buildConfig } from 'payload/config'
|
||||||
@@ -75,16 +87,18 @@ export default config
|
|||||||
|
|
||||||
#### Parent
|
#### Parent
|
||||||
|
|
||||||
The `parent` relationship field is automatically added to every document which allows editors to choose another document from the same collection to act as the direct parent.
|
The `parent` relationship field is automatically added to every document which allows editors to choose another document
|
||||||
|
from the same collection to act as the direct parent.
|
||||||
|
|
||||||
#### Breadcrumbs
|
#### Breadcrumbs
|
||||||
|
|
||||||
The `breadcrumbs` field is an array which dynamically populates all parent relationships of a document up to the top level and stores the following fields.
|
The `breadcrumbs` field is an array which dynamically populates all parent relationships of a document up to the top
|
||||||
|
level and stores the following fields.
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description |
|
||||||
| ------------ | --------------------------------------------------------------------------- |
|
|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `label` | The label of the breadcrumb. This field is automatically set to either the `collection.admin.useAsTitle` (if defined) or is set to the `ID` of the document. You can also dynamically define the `label` by passing a function to the options property of [`generateLabel`](#generateLabel). |
|
| `label` | The label of the breadcrumb. This field is automatically set to either the `collection.admin.useAsTitle` (if defined) or is set to the `ID` of the document. You can also dynamically define the `label` by passing a function to the options property of [`generateLabel`](#generateLabel). |
|
||||||
| `url` | The URL of the breadcrumb. By default, this field is undefined. You can manually define this field by passing a property called function to the plugin options property of [`generateURL`](#generateURL). |
|
| `url` | The URL of the breadcrumb. By default, this field is undefined. You can manually define this field by passing a property called function to the plugin options property of [`generateURL`](#generateURL). |
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
@@ -94,7 +108,8 @@ An array of collections slugs to enable nested docs.
|
|||||||
|
|
||||||
#### `generateLabel`
|
#### `generateLabel`
|
||||||
|
|
||||||
Each `breadcrumb` has a required `label` field. By default, its value will be set to the collection's `admin.useAsTitle` or fallback the the `ID` of the document.
|
Each `breadcrumb` has a required `label` field. By default, its value will be set to the collection's `admin.useAsTitle`
|
||||||
|
or fallback the the `ID` of the document.
|
||||||
|
|
||||||
You can also pass a function to dynamically set the `label` of your breadcrumb.
|
You can also pass a function to dynamically set the `label` of your breadcrumb.
|
||||||
|
|
||||||
@@ -108,14 +123,16 @@ nestedDocs({
|
|||||||
|
|
||||||
The function takes two arguments and returns a string:
|
The function takes two arguments and returns a string:
|
||||||
|
|
||||||
| Argument | Type | Description |
|
| Argument | Type | Description |
|
||||||
| ------------ | -------- | --------------------------------------------------------------------------- |
|
|----------|----------|----------------------------------------------|
|
||||||
| `docs` | `Array` | An array of the breadcrumbs up to that point |
|
| `docs` | `Array` | An array of the breadcrumbs up to that point |
|
||||||
| `doc` | `Object` | The current document being edited |
|
| `doc` | `Object` | The current document being edited |
|
||||||
|
|
||||||
#### `generateURL`
|
#### `generateURL`
|
||||||
|
|
||||||
A function that allows you to dynamically generate each breadcrumb `url`. Each `breadcrumb` has an optional `url` field which is undefined by default. For example, you might want to format a full URL to contain all of the breadcrumbs up to that point, like `/about-us/company/our-team`.
|
A function that allows you to dynamically generate each breadcrumb `url`. Each `breadcrumb` has an optional `url` field
|
||||||
|
which is undefined by default. For example, you might want to format a full URL to contain all breadcrumbs up to
|
||||||
|
that point, like `/about-us/company/our-team`.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// payload.config.ts
|
// payload.config.ts
|
||||||
@@ -125,18 +142,21 @@ nestedDocs({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
| Argument | Type | Description |
|
| Argument | Type | Description |
|
||||||
| ------------ | -------- | --------------------------------------------------------------------------- |
|
|----------|----------|----------------------------------------------|
|
||||||
| `docs` | `Array` | An array of the breadcrumbs up to that point |
|
| `docs` | `Array` | An array of the breadcrumbs up to that point |
|
||||||
| `doc` | `Object` | The current document being edited |
|
| `doc` | `Object` | The current document being edited |
|
||||||
|
|
||||||
#### `parentFieldSlug`
|
#### `parentFieldSlug`
|
||||||
|
|
||||||
When defined, the `parent` field will not be provided for you automatically, and instead, expects you to add your own `parent` field to each collection manually. This gives you complete control over where you put the field in your admin dashboard, etc. Set this property to the `name` of your custom field.
|
When defined, the `parent` field will not be provided for you automatically, and instead, expects you to add your
|
||||||
|
own `parent` field to each collection manually. This gives you complete control over where you put the field in your
|
||||||
|
admin dashboard, etc. Set this property to the `name` of your custom field.
|
||||||
|
|
||||||
#### `breadcrumbsFieldSlug`
|
#### `breadcrumbsFieldSlug`
|
||||||
|
|
||||||
When defined, the `breadcrumbs` field will not be provided for you, and instead, expects your to add your own `breadcrumbs` field to each collection manually. Set this property to the `name` of your custom field.
|
When defined, the `breadcrumbs` field will not be provided for you, and instead, expects you to add your
|
||||||
|
own `breadcrumbs` field to each collection manually. Set this property to the `name` of your custom field.
|
||||||
|
|
||||||
<Banner type="info">
|
<Banner type="info">
|
||||||
<strong>Note:</strong>
|
<strong>Note:</strong>
|
||||||
@@ -146,7 +166,8 @@ When defined, the `breadcrumbs` field will not be provided for you, and instead,
|
|||||||
|
|
||||||
## Overrides
|
## Overrides
|
||||||
|
|
||||||
You can also extend the built-in `parent` and `breadcrumbs` fields per collection by using the `createParentField` and `createBreadcrumbField` methods. They will merge your customizations overtop the plugin's base field configurations.
|
You can also extend the built-in `parent` and `breadcrumbs` fields per collection by using the `createParentField`
|
||||||
|
and `createBreadcrumbField` methods. They will merge your customizations overtop the plugin's base field configurations.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { CollectionConfig } from "payload/types";
|
import { CollectionConfig } from "payload/types";
|
||||||
@@ -187,9 +208,17 @@ const examplePageConfig: CollectionConfig = {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<Banner type="warning">
|
||||||
|
<strong>Note:</strong>
|
||||||
|
<br />
|
||||||
|
If overriding the `name` of either `breadcrumbs` or `parent` fields, you must specify the `breadcrumbsFieldSlug` or `parentFieldSlug` respectively.
|
||||||
|
</Banner>
|
||||||
|
|
||||||
## Localization
|
## Localization
|
||||||
|
|
||||||
This plugin supports localization by default. If the `localization` property is set in your Payload config, the `breadcrumbs` field is automatically localized. For more details on how localization works in Payload, see the [Localization](https://payloadcms.com/docs/localization/overview) docs.
|
This plugin supports localization by default. If the `localization` property is set in your Payload config,
|
||||||
|
the `breadcrumbs` field is automatically localized. For more details on how localization works in Payload, see
|
||||||
|
the [Localization](https://payloadcms.com/docs/localization/overview) docs.
|
||||||
|
|
||||||
## TypeScript
|
## TypeScript
|
||||||
|
|
||||||
@@ -201,4 +230,10 @@ import { PluginConfig, GenerateURL, GenerateLabel } from '@payloadcms/plugin-nes
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
The [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) contains an official [Nested Docs Plugin Example](https://github.com/payloadcms/payload/tree/main/examples/nested-docs) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website) and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommere), both of which use this plugin.
|
The [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) contains an
|
||||||
|
official [Nested Docs Plugin Example](https://github.com/payloadcms/payload/tree/main/examples/nested-docs) which
|
||||||
|
demonstrates exactly how to configure this plugin in Payload and implement it on your front-end.
|
||||||
|
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an
|
||||||
|
official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website)
|
||||||
|
and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommere), both of which use this
|
||||||
|
plugin.
|
||||||
|
|||||||
@@ -287,5 +287,5 @@ import {
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) contains an official [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommere) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. You can also check out [How to Build An E-Commerce Site With Next.js](https://payloadcms.com/blog/how-to-build-an-e-commerce-site-with-nextjs) post for a bit more context around this template.
|
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) contains an official [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. You can also check out [How to Build An E-Commerce Site With Next.js](https://payloadcms.com/blog/how-to-build-an-e-commerce-site-with-nextjs) post for a bit more context around this template.
|
||||||
|
|
||||||
|
|||||||
@@ -4301,9 +4301,9 @@ focus-trap@^6.9.2:
|
|||||||
tabbable "^5.3.3"
|
tabbable "^5.3.3"
|
||||||
|
|
||||||
follow-redirects@^1.15.2:
|
follow-redirects@^1.15.2:
|
||||||
version "1.15.2"
|
version "1.15.4"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf"
|
||||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==
|
||||||
|
|
||||||
for-each@^0.3.3:
|
for-each@^0.3.3:
|
||||||
version "0.3.3"
|
version "0.3.3"
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
"lexical": "0.12.5",
|
"lexical": "0.12.5",
|
||||||
"lint-staged": "^14.0.1",
|
"lint-staged": "^14.0.1",
|
||||||
"minimist": "1.2.8",
|
"minimist": "1.2.8",
|
||||||
"mongodb-memory-server": "8.13.0",
|
"mongodb-memory-server": "^9",
|
||||||
"node-fetch": "2.6.12",
|
"node-fetch": "2.6.12",
|
||||||
"nodemon": "3.0.2",
|
"nodemon": "3.0.2",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/db-mongodb",
|
"name": "@payloadcms/db-mongodb",
|
||||||
"version": "1.1.1",
|
"version": "1.3.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",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"bson-objectid": "2.0.4",
|
"bson-objectid": "2.0.4",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"get-port": "5.1.1",
|
"get-port": "5.1.1",
|
||||||
"mongoose": "6.12.0",
|
"mongoose": "6.12.3",
|
||||||
"mongoose-aggregate-paginate-v2": "1.0.6",
|
"mongoose-aggregate-paginate-v2": "1.0.6",
|
||||||
"mongoose-paginate-v2": "1.7.22",
|
"mongoose-paginate-v2": "1.7.22",
|
||||||
"prompts": "2.4.2",
|
"prompts": "2.4.2",
|
||||||
@@ -33,7 +33,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
"@types/mongoose-aggregate-paginate-v2": "1.0.9",
|
"@types/mongoose-aggregate-paginate-v2": "1.0.9",
|
||||||
"mongodb-memory-server": "8.13.0",
|
"mongodb": "4.17.1",
|
||||||
|
"mongodb-memory-server": "^9",
|
||||||
"payload": "workspace:*"
|
"payload": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ export const connect: Connect = async function connect(this: MongooseAdapter, pa
|
|||||||
try {
|
try {
|
||||||
this.connection = (await mongoose.connect(urlToConnect, connectionOptions)).connection
|
this.connection = (await mongoose.connect(urlToConnect, connectionOptions)).connection
|
||||||
|
|
||||||
|
const client = this.connection.getClient()
|
||||||
|
|
||||||
|
if (!client.options.replicaSet || this.transactionOptions === false) {
|
||||||
|
this.transactionOptions = false
|
||||||
|
this.beginTransaction = undefined
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
|
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
|
||||||
this.payload.logger.info('---- DROPPING DATABASE ----')
|
this.payload.logger.info('---- DROPPING DATABASE ----')
|
||||||
await mongoose.connection.dropDatabase()
|
await mongoose.connection.dropDatabase()
|
||||||
|
|||||||
@@ -55,8 +55,11 @@ export const find: Find = async function find(
|
|||||||
useEstimatedCount,
|
useEstimatedCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useEstimatedCount && this.disableIndexHints !== true) {
|
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding a hint.
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
paginationOptions.useCustomCountFn = () => {
|
paginationOptions.useCustomCountFn = () => {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
Model.countDocuments(query, {
|
Model.countDocuments(query, {
|
||||||
|
|||||||
@@ -74,8 +74,11 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
|||||||
useEstimatedCount,
|
useEstimatedCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useEstimatedCount && this.disableIndexHints !== true) {
|
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding a hint.
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
paginationOptions.useCustomCountFn = () => {
|
paginationOptions.useCustomCountFn = () => {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
Model.countDocuments(query, {
|
Model.countDocuments(query, {
|
||||||
|
|||||||
@@ -71,8 +71,11 @@ export const findVersions: FindVersions = async function findVersions(
|
|||||||
useEstimatedCount,
|
useEstimatedCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useEstimatedCount && this.disableIndexHints !== true) {
|
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding a hint.
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
paginationOptions.useCustomCountFn = () => {
|
paginationOptions.useCustomCountFn = () => {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
Model.countDocuments(query, {
|
Model.countDocuments(query, {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { TransactionOptions } from 'mongodb'
|
||||||
import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
|
import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
|
||||||
import type { Payload } from 'payload'
|
import type { Payload } from 'payload'
|
||||||
import type { BaseDatabaseAdapter } from 'payload/database'
|
import type { BaseDatabaseAdapter } from 'payload/database'
|
||||||
@@ -7,8 +8,6 @@ import mongoose from 'mongoose'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { createDatabaseAdapter } from 'payload/database'
|
import { createDatabaseAdapter } from 'payload/database'
|
||||||
|
|
||||||
export type { MigrateDownArgs, MigrateUpArgs } from './types'
|
|
||||||
|
|
||||||
import type { CollectionModel, GlobalModel } from './types'
|
import type { CollectionModel, GlobalModel } from './types'
|
||||||
|
|
||||||
import { connect } from './connect'
|
import { connect } from './connect'
|
||||||
@@ -39,6 +38,8 @@ import { updateGlobalVersion } from './updateGlobalVersion'
|
|||||||
import { updateOne } from './updateOne'
|
import { updateOne } from './updateOne'
|
||||||
import { updateVersion } from './updateVersion'
|
import { updateVersion } from './updateVersion'
|
||||||
|
|
||||||
|
export type { MigrateDownArgs, MigrateUpArgs } from './types'
|
||||||
|
|
||||||
export interface Args {
|
export interface Args {
|
||||||
/** Set to false to disable auto-pluralization of collection names, Defaults to true */
|
/** Set to false to disable auto-pluralization of collection names, Defaults to true */
|
||||||
autoPluralization?: boolean
|
autoPluralization?: boolean
|
||||||
@@ -50,6 +51,7 @@ export interface Args {
|
|||||||
/** Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false */
|
/** Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false */
|
||||||
disableIndexHints?: boolean
|
disableIndexHints?: boolean
|
||||||
migrationDir?: string
|
migrationDir?: string
|
||||||
|
transactionOptions?: TransactionOptions | false
|
||||||
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
|
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
|
||||||
url: false | string
|
url: false | string
|
||||||
}
|
}
|
||||||
@@ -81,6 +83,7 @@ declare module 'payload' {
|
|||||||
globals: GlobalModel
|
globals: GlobalModel
|
||||||
mongoMemoryServer: any
|
mongoMemoryServer: any
|
||||||
sessions: Record<number | string, ClientSession>
|
sessions: Record<number | string, ClientSession>
|
||||||
|
transactionOptions: TransactionOptions
|
||||||
versions: {
|
versions: {
|
||||||
[slug: string]: CollectionModel
|
[slug: string]: CollectionModel
|
||||||
}
|
}
|
||||||
@@ -92,15 +95,21 @@ export function mongooseAdapter({
|
|||||||
connectOptions,
|
connectOptions,
|
||||||
disableIndexHints = false,
|
disableIndexHints = false,
|
||||||
migrationDir: migrationDirArg,
|
migrationDir: migrationDirArg,
|
||||||
|
transactionOptions,
|
||||||
url,
|
url,
|
||||||
}: Args): MongooseAdapterResult {
|
}: Args): MongooseAdapterResult {
|
||||||
function adapter({ payload }: { payload: Payload }) {
|
function adapter({ payload }: { payload: Payload }) {
|
||||||
const migrationDir = findMigrationDir(migrationDirArg)
|
const migrationDir = findMigrationDir(migrationDirArg)
|
||||||
|
let beginTransactionFunction = beginTransaction
|
||||||
mongoose.set('strictQuery', false)
|
mongoose.set('strictQuery', false)
|
||||||
|
|
||||||
extendWebpackConfig(payload.config)
|
extendWebpackConfig(payload.config)
|
||||||
extendViteConfig(payload.config)
|
extendViteConfig(payload.config)
|
||||||
|
|
||||||
|
if (transactionOptions === false) {
|
||||||
|
beginTransactionFunction = () => null
|
||||||
|
}
|
||||||
|
|
||||||
return createDatabaseAdapter<MongooseAdapter>({
|
return createDatabaseAdapter<MongooseAdapter>({
|
||||||
name: 'mongoose',
|
name: 'mongoose',
|
||||||
|
|
||||||
@@ -113,11 +122,12 @@ export function mongooseAdapter({
|
|||||||
globals: undefined,
|
globals: undefined,
|
||||||
mongoMemoryServer: undefined,
|
mongoMemoryServer: undefined,
|
||||||
sessions: {},
|
sessions: {},
|
||||||
|
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
|
||||||
url,
|
url,
|
||||||
versions: {},
|
versions: {},
|
||||||
|
|
||||||
// DatabaseAdapter
|
// DatabaseAdapter
|
||||||
beginTransaction,
|
beginTransaction: beginTransactionFunction,
|
||||||
commitTransaction,
|
commitTransaction,
|
||||||
connect,
|
connect,
|
||||||
create,
|
create,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { PayloadRequest } from 'payload/types'
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
import { readMigrationFiles } from 'payload/database'
|
import { readMigrationFiles } from 'payload/database'
|
||||||
|
import { commitTransaction } from 'payload/dist/utilities/commitTransaction'
|
||||||
|
import { initTransaction } from 'payload/dist/utilities/initTransaction'
|
||||||
|
import { killTransaction } from 'payload/dist/utilities/killTransaction'
|
||||||
import prompts from 'prompts'
|
import prompts from 'prompts'
|
||||||
|
|
||||||
import type { MongooseAdapter } from '.'
|
import type { MongooseAdapter } from '.'
|
||||||
@@ -14,9 +17,9 @@ export async function migrateFresh(this: MongooseAdapter): Promise<void> {
|
|||||||
const { confirm: acceptWarning } = await prompts(
|
const { confirm: acceptWarning } = await prompts(
|
||||||
{
|
{
|
||||||
name: 'confirm',
|
name: 'confirm',
|
||||||
|
type: 'confirm',
|
||||||
initial: false,
|
initial: false,
|
||||||
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
||||||
type: 'confirm',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
@@ -40,29 +43,29 @@ export async function migrateFresh(this: MongooseAdapter): Promise<void> {
|
|||||||
msg: `Found ${migrationFiles.length} migration files.`,
|
msg: `Found ${migrationFiles.length} migration files.`,
|
||||||
})
|
})
|
||||||
|
|
||||||
let transactionID
|
const req = {} as PayloadRequest
|
||||||
|
|
||||||
// Run all migrate up
|
// Run all migrate up
|
||||||
for (const migration of migrationFiles) {
|
for (const migration of migrationFiles) {
|
||||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||||
try {
|
try {
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
transactionID = await this.beginTransaction()
|
await initTransaction(req)
|
||||||
await migration.up({ payload })
|
await migration.up({ payload, req })
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'payload-migrations',
|
collection: 'payload-migrations',
|
||||||
data: {
|
data: {
|
||||||
name: migration.name,
|
name: migration.name,
|
||||||
batch: 1,
|
batch: 1,
|
||||||
},
|
},
|
||||||
req: {
|
req,
|
||||||
transactionID,
|
|
||||||
} as PayloadRequest,
|
|
||||||
})
|
})
|
||||||
await this.commitTransaction(transactionID)
|
|
||||||
|
await commitTransaction(req)
|
||||||
|
|
||||||
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
await this.rollbackTransaction(transactionID)
|
await killTransaction(req)
|
||||||
payload.logger.error({
|
payload.logger.error({
|
||||||
err,
|
err,
|
||||||
msg: `Error running migration ${migration.name}. Rolling back.`,
|
msg: `Error running migration ${migration.name}. Rolling back.`,
|
||||||
|
|||||||
@@ -547,7 +547,10 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
|||||||
config: SanitizedConfig,
|
config: SanitizedConfig,
|
||||||
buildSchemaOptions: BuildSchemaOptions,
|
buildSchemaOptions: BuildSchemaOptions,
|
||||||
): void => {
|
): void => {
|
||||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
|
const baseSchema = {
|
||||||
|
...formatBaseSchema(field, buildSchemaOptions),
|
||||||
|
type: field.hasMany ? [String] : String,
|
||||||
|
}
|
||||||
|
|
||||||
schema.add({
|
schema.add({
|
||||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||||
|
|||||||
@@ -58,8 +58,15 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
|||||||
useEstimatedCount,
|
useEstimatedCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useEstimatedCount && this.disableIndexHints !== true) {
|
if (
|
||||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding a hint.
|
!useEstimatedCount &&
|
||||||
|
Object.keys(versionQuery).length === 0 &&
|
||||||
|
this.disableIndexHints !== true
|
||||||
|
) {
|
||||||
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
paginationOptions.useCustomCountFn = () => {
|
paginationOptions.useCustomCountFn = () => {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
VersionModel.countDocuments(versionQuery, {
|
VersionModel.countDocuments(versionQuery, {
|
||||||
|
|||||||
@@ -1,34 +1,30 @@
|
|||||||
// @ts-expect-error // TODO: Fix this import
|
|
||||||
import type { TransactionOptions } from 'mongodb'
|
import type { TransactionOptions } from 'mongodb'
|
||||||
import type { BeginTransaction } from 'payload/database'
|
import type { BeginTransaction } from 'payload/database'
|
||||||
|
|
||||||
import { APIError } from 'payload/errors'
|
import { APIError } from 'payload/errors'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
let transactionsNotAvailable: boolean
|
import type { MongooseAdapter } from '../index'
|
||||||
|
|
||||||
export const beginTransaction: BeginTransaction = async function beginTransaction(
|
export const beginTransaction: BeginTransaction = async function beginTransaction(
|
||||||
options: TransactionOptions = {},
|
this: MongooseAdapter,
|
||||||
|
options: TransactionOptions,
|
||||||
) {
|
) {
|
||||||
let id = null
|
|
||||||
if (!this.connection) {
|
if (!this.connection) {
|
||||||
throw new APIError('beginTransaction called while no connection to the database exists')
|
throw new APIError('beginTransaction called while no connection to the database exists')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionsNotAvailable) return id
|
|
||||||
|
|
||||||
const client = this.connection.getClient()
|
const client = this.connection.getClient()
|
||||||
if (!client.options.replicaSet) {
|
const id = uuid()
|
||||||
transactionsNotAvailable = true
|
|
||||||
} else {
|
if (!this.sessions[id]) {
|
||||||
id = uuid()
|
this.sessions[id] = client.startSession()
|
||||||
if (!this.sessions[id]) {
|
|
||||||
this.sessions[id] = await client.startSession()
|
|
||||||
}
|
|
||||||
if (this.sessions[id].inTransaction()) {
|
|
||||||
this.payload.logger.warn('beginTransaction called while transaction already exists')
|
|
||||||
} else {
|
|
||||||
await this.sessions[id].startTransaction(options)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (this.sessions[id].inTransaction()) {
|
||||||
|
this.payload.logger.warn('beginTransaction called while transaction already exists')
|
||||||
|
} else {
|
||||||
|
this.sessions[id].startTransaction(options || (this.transactionOptions as TransactionOptions))
|
||||||
|
}
|
||||||
|
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/db-postgres",
|
"name": "@payloadcms/db-postgres",
|
||||||
"version": "0.2.2",
|
"version": "0.3.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -33,6 +33,16 @@ export const buildFindManyArgs = ({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (adapter.tables[`${tableName}_texts`]) {
|
||||||
|
result.with._texts = {
|
||||||
|
columns: {
|
||||||
|
id: false,
|
||||||
|
parent: false,
|
||||||
|
},
|
||||||
|
orderBy: ({ order }, { asc: ASC }) => [ASC(order)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (adapter.tables[`${tableName}_numbers`]) {
|
if (adapter.tables[`${tableName}_numbers`]) {
|
||||||
result.with._numbers = {
|
result.with._numbers = {
|
||||||
columns: {
|
columns: {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import toSnakeCase from 'to-snake-case'
|
|||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { buildTable } from './schema/build'
|
import { buildTable } from './schema/build'
|
||||||
|
import { getConfigIDType } from './schema/getConfigIDType'
|
||||||
|
|
||||||
export const init: Init = async function init(this: PostgresAdapter) {
|
export const init: Init = async function init(this: PostgresAdapter) {
|
||||||
if (this.payload.config.localization) {
|
if (this.payload.config.localization) {
|
||||||
@@ -23,6 +24,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
|
buildTexts: true,
|
||||||
buildNumbers: true,
|
buildNumbers: true,
|
||||||
buildRelationships: true,
|
buildRelationships: true,
|
||||||
disableNotNull: !!collection?.versions?.drafts,
|
disableNotNull: !!collection?.versions?.drafts,
|
||||||
@@ -36,8 +38,11 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
const versionsTableName = `_${tableName}_v`
|
const versionsTableName = `_${tableName}_v`
|
||||||
const versionFields = buildVersionCollectionFields(collection)
|
const versionFields = buildVersionCollectionFields(collection)
|
||||||
|
|
||||||
|
const versionsParentIDColType = getConfigIDType(collection.fields)
|
||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
|
buildTexts: true,
|
||||||
buildNumbers: true,
|
buildNumbers: true,
|
||||||
buildRelationships: true,
|
buildRelationships: true,
|
||||||
disableNotNull: !!collection.versions?.drafts,
|
disableNotNull: !!collection.versions?.drafts,
|
||||||
@@ -54,6 +59,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
|
buildTexts: true,
|
||||||
buildNumbers: true,
|
buildNumbers: true,
|
||||||
buildRelationships: true,
|
buildRelationships: true,
|
||||||
disableNotNull: !!global?.versions?.drafts,
|
disableNotNull: !!global?.versions?.drafts,
|
||||||
@@ -69,6 +75,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
|
buildTexts: true,
|
||||||
buildNumbers: true,
|
buildNumbers: true,
|
||||||
buildRelationships: true,
|
buildRelationships: true,
|
||||||
disableNotNull: !!global.versions?.drafts,
|
disableNotNull: !!global.versions?.drafts,
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||||
import type { Payload } from 'payload'
|
import type { Payload } from 'payload'
|
||||||
import type { Migration } from 'payload/database'
|
import type { Migration } from 'payload/database'
|
||||||
|
import type { PayloadRequest } from 'payload/dist/express/types'
|
||||||
|
|
||||||
import { readMigrationFiles } from 'payload/database'
|
import { readMigrationFiles } from 'payload/database'
|
||||||
|
import { commitTransaction } from 'payload/dist/utilities/commitTransaction'
|
||||||
|
import { initTransaction } from 'payload/dist/utilities/initTransaction'
|
||||||
|
import { killTransaction } from 'payload/dist/utilities/killTransaction'
|
||||||
import prompts from 'prompts'
|
import prompts from 'prompts'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
@@ -42,11 +46,11 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
|
|||||||
const { confirm: runMigrations } = await prompts(
|
const { confirm: runMigrations } = await prompts(
|
||||||
{
|
{
|
||||||
name: 'confirm',
|
name: 'confirm',
|
||||||
|
type: 'confirm',
|
||||||
initial: false,
|
initial: false,
|
||||||
message:
|
message:
|
||||||
"It looks like you've run Payload in dev mode, meaning you've dynamically pushed changes to your database.\n\n" +
|
"It looks like you've run Payload in dev mode, meaning you've dynamically pushed changes to your database.\n\n" +
|
||||||
"If you'd like to run migrations, data loss will occur. Would you like to proceed?",
|
"If you'd like to run migrations, data loss will occur. Would you like to proceed?",
|
||||||
type: 'confirm',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
@@ -79,6 +83,7 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n
|
|||||||
const { generateDrizzleJson } = require('drizzle-kit/utils')
|
const { generateDrizzleJson } = require('drizzle-kit/utils')
|
||||||
|
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
|
const req = {} as PayloadRequest
|
||||||
|
|
||||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||||
|
|
||||||
@@ -86,7 +91,8 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n
|
|||||||
const drizzleJSON = generateDrizzleJson(pgAdapter.schema)
|
const drizzleJSON = generateDrizzleJson(pgAdapter.schema)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await migration.up({ payload })
|
await initTransaction(req)
|
||||||
|
await migration.up({ payload, req })
|
||||||
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'payload-migrations',
|
collection: 'payload-migrations',
|
||||||
@@ -95,8 +101,11 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n
|
|||||||
batch,
|
batch,
|
||||||
schema: drizzleJSON,
|
schema: drizzleJSON,
|
||||||
},
|
},
|
||||||
|
req,
|
||||||
})
|
})
|
||||||
|
await commitTransaction(req)
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
await killTransaction(req)
|
||||||
payload.logger.error({
|
payload.logger.error({
|
||||||
err,
|
err,
|
||||||
msg: parseError(err, `Error running migration ${migration.name}`),
|
msg: parseError(err, `Error running migration ${migration.name}`),
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
import type { PayloadRequest } from 'payload/types'
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
import { getMigrations, readMigrationFiles } from 'payload/database'
|
import { getMigrations, readMigrationFiles } from 'payload/database'
|
||||||
|
import { commitTransaction } from 'payload/dist/utilities/commitTransaction'
|
||||||
|
import { initTransaction } from 'payload/dist/utilities/initTransaction'
|
||||||
|
import { killTransaction } from 'payload/dist/utilities/killTransaction'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
@@ -25,19 +28,21 @@ export async function migrateDown(this: PostgresAdapter): 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.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
let transactionID
|
const req = {} as PayloadRequest
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
|
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
|
||||||
transactionID = await this.beginTransaction()
|
await initTransaction(req)
|
||||||
await migrationFile.down({ payload })
|
await migrationFile.down({ payload, req })
|
||||||
payload.logger.info({
|
payload.logger.info({
|
||||||
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
|
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
|
||||||
})
|
})
|
||||||
@@ -47,15 +52,13 @@ export async function migrateDown(this: PostgresAdapter): Promise<void> {
|
|||||||
await payload.delete({
|
await payload.delete({
|
||||||
id: migration.id,
|
id: migration.id,
|
||||||
collection: 'payload-migrations',
|
collection: 'payload-migrations',
|
||||||
req: {
|
req,
|
||||||
transactionID,
|
|
||||||
} as PayloadRequest,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.commitTransaction(transactionID)
|
await commitTransaction(req)
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
await this.rollbackTransaction(transactionID)
|
await killTransaction(req)
|
||||||
|
|
||||||
payload.logger.error({
|
payload.logger.error({
|
||||||
err,
|
err,
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import type { PayloadRequest } from 'payload/types'
|
|||||||
|
|
||||||
import { sql } from 'drizzle-orm'
|
import { sql } from 'drizzle-orm'
|
||||||
import { readMigrationFiles } from 'payload/database'
|
import { readMigrationFiles } from 'payload/database'
|
||||||
|
import { commitTransaction } from 'payload/dist/utilities/commitTransaction'
|
||||||
|
import { initTransaction } from 'payload/dist/utilities/initTransaction'
|
||||||
|
import { killTransaction } from 'payload/dist/utilities/killTransaction'
|
||||||
import prompts from 'prompts'
|
import prompts from 'prompts'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
@@ -17,9 +20,9 @@ export async function migrateFresh(this: PostgresAdapter): Promise<void> {
|
|||||||
const { confirm: acceptWarning } = await prompts(
|
const { confirm: acceptWarning } = await prompts(
|
||||||
{
|
{
|
||||||
name: 'confirm',
|
name: 'confirm',
|
||||||
|
type: 'confirm',
|
||||||
initial: false,
|
initial: false,
|
||||||
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
||||||
type: 'confirm',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
@@ -36,36 +39,35 @@ export async function migrateFresh(this: PostgresAdapter): Promise<void> {
|
|||||||
msg: `Dropping database.`,
|
msg: `Dropping database.`,
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.drizzle.execute(sql`drop schema public cascade;\ncreate schema public;`)
|
await this.drizzle.execute(sql`drop schema public cascade;
|
||||||
|
create schema public;`)
|
||||||
|
|
||||||
const migrationFiles = await readMigrationFiles({ payload })
|
const migrationFiles = await readMigrationFiles({ payload })
|
||||||
payload.logger.debug({
|
payload.logger.debug({
|
||||||
msg: `Found ${migrationFiles.length} migration files.`,
|
msg: `Found ${migrationFiles.length} migration files.`,
|
||||||
})
|
})
|
||||||
|
|
||||||
let transactionID
|
const req = {} as PayloadRequest
|
||||||
// Run all migrate up
|
// Run all migrate up
|
||||||
for (const migration of migrationFiles) {
|
for (const migration of migrationFiles) {
|
||||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||||
try {
|
try {
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
transactionID = await this.beginTransaction()
|
await initTransaction(req)
|
||||||
await migration.up({ payload })
|
await migration.up({ payload, req })
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'payload-migrations',
|
collection: 'payload-migrations',
|
||||||
data: {
|
data: {
|
||||||
name: migration.name,
|
name: migration.name,
|
||||||
batch: 1,
|
batch: 1,
|
||||||
},
|
},
|
||||||
req: {
|
req,
|
||||||
transactionID,
|
|
||||||
} as PayloadRequest,
|
|
||||||
})
|
})
|
||||||
await this.commitTransaction(transactionID)
|
await commitTransaction(req)
|
||||||
|
|
||||||
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
await this.rollbackTransaction(transactionID)
|
await killTransaction(req)
|
||||||
payload.logger.error({
|
payload.logger.error({
|
||||||
err,
|
err,
|
||||||
msg: parseError(err, `Error running migration ${migration.name}. Rolling back`),
|
msg: parseError(err, `Error running migration ${migration.name}. Rolling back`),
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
import type { PayloadRequest } from 'payload/types'
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
import { getMigrations, readMigrationFiles } from 'payload/database'
|
import { getMigrations, readMigrationFiles } from 'payload/database'
|
||||||
import { DatabaseError } from 'pg'
|
import { commitTransaction } from 'payload/dist/utilities/commitTransaction'
|
||||||
|
import { initTransaction } from 'payload/dist/utilities/initTransaction'
|
||||||
|
import { killTransaction } from 'payload/dist/utilities/killTransaction'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ export async function migrateRefresh(this: PostgresAdapter) {
|
|||||||
msg: `Rolling back batch ${latestBatch} consisting of ${existingMigrations.length} migration(s).`,
|
msg: `Rolling back batch ${latestBatch} consisting of ${existingMigrations.length} migration(s).`,
|
||||||
})
|
})
|
||||||
|
|
||||||
let transactionID
|
const req = {} as PayloadRequest
|
||||||
|
|
||||||
// Reverse order of migrations to rollback
|
// Reverse order of migrations to rollback
|
||||||
existingMigrations.reverse()
|
existingMigrations.reverse()
|
||||||
@@ -43,8 +45,8 @@ export async function migrateRefresh(this: PostgresAdapter) {
|
|||||||
|
|
||||||
payload.logger.info({ msg: `Migrating down: ${migration.name}` })
|
payload.logger.info({ msg: `Migrating down: ${migration.name}` })
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
transactionID = await this.beginTransaction()
|
await initTransaction(req)
|
||||||
await migrationFile.down({ payload })
|
await migrationFile.down({ payload, req })
|
||||||
payload.logger.info({
|
payload.logger.info({
|
||||||
msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`,
|
msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`,
|
||||||
})
|
})
|
||||||
@@ -53,9 +55,7 @@ export async function migrateRefresh(this: PostgresAdapter) {
|
|||||||
if (tableExists) {
|
if (tableExists) {
|
||||||
await payload.delete({
|
await payload.delete({
|
||||||
collection: 'payload-migrations',
|
collection: 'payload-migrations',
|
||||||
req: {
|
req,
|
||||||
transactionID,
|
|
||||||
} as PayloadRequest,
|
|
||||||
where: {
|
where: {
|
||||||
name: {
|
name: {
|
||||||
equals: migration.name,
|
equals: migration.name,
|
||||||
@@ -63,8 +63,9 @@ export async function migrateRefresh(this: PostgresAdapter) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
await commitTransaction(req)
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
await this.rollbackTransaction(transactionID)
|
await killTransaction(req)
|
||||||
payload.logger.error({
|
payload.logger.error({
|
||||||
err,
|
err,
|
||||||
msg: parseError(err, `Error running migration ${migration.name}. Rolling back.`),
|
msg: parseError(err, `Error running migration ${migration.name}. Rolling back.`),
|
||||||
@@ -78,23 +79,21 @@ export async function migrateRefresh(this: PostgresAdapter) {
|
|||||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||||
try {
|
try {
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
transactionID = await this.beginTransaction()
|
await initTransaction(req)
|
||||||
await migration.up({ payload })
|
await migration.up({ payload, req })
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'payload-migrations',
|
collection: 'payload-migrations',
|
||||||
data: {
|
data: {
|
||||||
name: migration.name,
|
name: migration.name,
|
||||||
executed: true,
|
executed: true,
|
||||||
},
|
},
|
||||||
req: {
|
req,
|
||||||
transactionID,
|
|
||||||
} as PayloadRequest,
|
|
||||||
})
|
})
|
||||||
await this.commitTransaction(transactionID)
|
await commitTransaction(req)
|
||||||
|
|
||||||
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
await this.rollbackTransaction(transactionID)
|
await killTransaction(req)
|
||||||
payload.logger.error({
|
payload.logger.error({
|
||||||
err,
|
err,
|
||||||
msg: parseError(err, `Error running migration ${migration.name}. Rolling back.`),
|
msg: parseError(err, `Error running migration ${migration.name}. Rolling back.`),
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
import type { PayloadRequest } from 'payload/types'
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
import { getMigrations, readMigrationFiles } from 'payload/database'
|
import { getMigrations, readMigrationFiles } from 'payload/database'
|
||||||
|
import { commitTransaction } from 'payload/dist/utilities/commitTransaction'
|
||||||
|
import { initTransaction } from 'payload/dist/utilities/initTransaction'
|
||||||
|
import { killTransaction } from 'payload/dist/utilities/killTransaction'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
@@ -21,10 +24,10 @@ export async function migrateReset(this: PostgresAdapter): Promise<void> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const req = {} as PayloadRequest
|
||||||
|
|
||||||
// Rollback all migrations in order
|
// Rollback all migrations in order
|
||||||
for (const migration of existingMigrations) {
|
for (const migration of existingMigrations) {
|
||||||
let transactionID
|
|
||||||
|
|
||||||
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
|
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
|
||||||
try {
|
try {
|
||||||
if (!migrationFile) {
|
if (!migrationFile) {
|
||||||
@@ -33,8 +36,8 @@ export async function migrateReset(this: PostgresAdapter): Promise<void> {
|
|||||||
|
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
|
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
|
||||||
transactionID = await this.beginTransaction()
|
await initTransaction(req)
|
||||||
await migrationFile.down({ payload })
|
await migrationFile.down({ payload, req })
|
||||||
payload.logger.info({
|
payload.logger.info({
|
||||||
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
|
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
|
||||||
})
|
})
|
||||||
@@ -44,19 +47,17 @@ export async function migrateReset(this: PostgresAdapter): Promise<void> {
|
|||||||
await payload.delete({
|
await payload.delete({
|
||||||
id: migration.id,
|
id: migration.id,
|
||||||
collection: 'payload-migrations',
|
collection: 'payload-migrations',
|
||||||
req: {
|
req,
|
||||||
transactionID,
|
|
||||||
} as PayloadRequest,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.commitTransaction(transactionID)
|
await commitTransaction(req)
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
let msg = `Error running migration ${migrationFile.name}.`
|
let msg = `Error running migration ${migrationFile.name}.`
|
||||||
|
|
||||||
if (err instanceof Error) msg += ` ${err.message}`
|
if (err instanceof Error) msg += ` ${err.message}`
|
||||||
|
|
||||||
await this.rollbackTransaction(transactionID)
|
await killTransaction(req)
|
||||||
payload.logger.error({
|
payload.logger.error({
|
||||||
err,
|
err,
|
||||||
msg,
|
msg,
|
||||||
|
|||||||
@@ -107,7 +107,11 @@ export async function parseParams({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (['json', 'richText'].includes(field.type) && Array.isArray(pathSegments)) {
|
if (
|
||||||
|
['json', 'richText'].includes(field.type) &&
|
||||||
|
Array.isArray(pathSegments) &&
|
||||||
|
pathSegments.length > 1
|
||||||
|
) {
|
||||||
const segments = pathSegments.slice(1)
|
const segments = pathSegments.slice(1)
|
||||||
segments.unshift(table[columnName].name)
|
segments.unshift(table[columnName].name)
|
||||||
|
|
||||||
@@ -121,12 +125,28 @@ export async function parseParams({
|
|||||||
})
|
})
|
||||||
|
|
||||||
constraints.push(sql.raw(jsonQuery))
|
constraints.push(sql.raw(jsonQuery))
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'json') {
|
const jsonQuery = convertPathToJSONTraversal(pathSegments)
|
||||||
const jsonQuery = convertPathToJSONTraversal(pathSegments)
|
const operatorKeys = {
|
||||||
constraints.push(sql.raw(`${table[columnName].name}${jsonQuery} = '%${val}%'`))
|
contains: { operator: 'ilike', wildcard: '%' },
|
||||||
|
equals: { operator: '=', wildcard: '' },
|
||||||
|
exists: { operator: val === true ? 'is not null' : 'is null' },
|
||||||
|
like: { operator: 'like', wildcard: '%' },
|
||||||
|
not_equals: { operator: '<>', wildcard: '' },
|
||||||
}
|
}
|
||||||
|
let formattedValue = `'${operatorKeys[operator].wildcard}${val}${operatorKeys[operator].wildcard}'`
|
||||||
|
|
||||||
|
if (operator === 'exists') {
|
||||||
|
formattedValue = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
constraints.push(
|
||||||
|
sql.raw(
|
||||||
|
`${table[columnName].name}${jsonQuery} ${operatorKeys[operator].operator} ${formattedValue}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import toSnakeCase from 'to-snake-case'
|
|||||||
|
|
||||||
import type { GenericColumns, GenericTable, PostgresAdapter } from '../types'
|
import type { GenericColumns, GenericTable, PostgresAdapter } from '../types'
|
||||||
|
|
||||||
|
import { getConfigIDType } from './getConfigIDType'
|
||||||
import { parentIDColumnMap } from './parentIDColumnMap'
|
import { parentIDColumnMap } from './parentIDColumnMap'
|
||||||
import { traverseFields } from './traverseFields'
|
import { traverseFields } from './traverseFields'
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ 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
|
||||||
disableNotNull: boolean
|
disableNotNull: boolean
|
||||||
@@ -40,6 +42,7 @@ type Args = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
|
hasManyTextField: 'index' | boolean
|
||||||
hasManyNumberField: 'index' | boolean
|
hasManyNumberField: 'index' | boolean
|
||||||
relationsToBuild: Map<string, string>
|
relationsToBuild: Map<string, string>
|
||||||
}
|
}
|
||||||
@@ -48,6 +51,7 @@ export const buildTable = ({
|
|||||||
adapter,
|
adapter,
|
||||||
baseColumns = {},
|
baseColumns = {},
|
||||||
baseExtraConfig = {},
|
baseExtraConfig = {},
|
||||||
|
buildTexts,
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
disableNotNull,
|
disableNotNull,
|
||||||
@@ -66,12 +70,15 @@ export const buildTable = ({
|
|||||||
|
|
||||||
let hasLocalizedField = false
|
let hasLocalizedField = false
|
||||||
let hasLocalizedRelationshipField = false
|
let hasLocalizedRelationshipField = false
|
||||||
|
let hasManyTextField: 'index' | boolean = false
|
||||||
let hasManyNumberField: 'index' | boolean = false
|
let hasManyNumberField: 'index' | boolean = false
|
||||||
|
let hasLocalizedManyTextField = false
|
||||||
let hasLocalizedManyNumberField = false
|
let hasLocalizedManyNumberField = false
|
||||||
|
|
||||||
const localesColumns: Record<string, PgColumnBuilder> = {}
|
const localesColumns: Record<string, PgColumnBuilder> = {}
|
||||||
const localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
const localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
||||||
let localesTable: GenericTable
|
let localesTable: GenericTable
|
||||||
|
let textsTable: GenericTable
|
||||||
let numbersTable: GenericTable
|
let numbersTable: GenericTable
|
||||||
|
|
||||||
// Relationships to the base collection
|
// Relationships to the base collection
|
||||||
@@ -82,30 +89,25 @@ export const buildTable = ({
|
|||||||
// Drizzle relations
|
// Drizzle relations
|
||||||
const relationsToBuild: Map<string, string> = new Map()
|
const relationsToBuild: Map<string, string> = new Map()
|
||||||
|
|
||||||
const idField = fields.find((field) => fieldAffectsData(field) && field.name === 'id')
|
const idColType = getConfigIDType(fields)
|
||||||
let idColType = 'integer'
|
|
||||||
|
|
||||||
if (idField) {
|
const idColTypeMap = {
|
||||||
if (idField.type === 'number') {
|
integer: serial,
|
||||||
idColType = 'numeric'
|
numeric,
|
||||||
columns.id = numeric('id').primaryKey()
|
varchar,
|
||||||
}
|
|
||||||
|
|
||||||
if (idField.type === 'text') {
|
|
||||||
idColType = 'varchar'
|
|
||||||
columns.id = varchar('id').primaryKey()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
columns.id = serial('id').primaryKey()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
columns.id = idColTypeMap[idColType]('id').primaryKey()
|
||||||
;({
|
;({
|
||||||
hasLocalizedField,
|
hasLocalizedField,
|
||||||
|
hasLocalizedManyTextField,
|
||||||
hasLocalizedManyNumberField,
|
hasLocalizedManyNumberField,
|
||||||
hasLocalizedRelationshipField,
|
hasLocalizedRelationshipField,
|
||||||
|
hasManyTextField,
|
||||||
hasManyNumberField,
|
hasManyNumberField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
|
buildTexts,
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
columns,
|
columns,
|
||||||
@@ -190,6 +192,50 @@ export const buildTable = ({
|
|||||||
adapter.relations[`relations_${localeTableName}`] = localesTableRelations
|
adapter.relations[`relations_${localeTableName}`] = localesTableRelations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasManyTextField && buildTexts) {
|
||||||
|
const textsTableName = `${rootTableName}_texts`
|
||||||
|
const columns: Record<string, PgColumnBuilder> = {
|
||||||
|
id: serial('id').primaryKey(),
|
||||||
|
text: varchar('text'),
|
||||||
|
order: integer('order').notNull(),
|
||||||
|
parent: parentIDColumnMap[idColType]('parent_id')
|
||||||
|
.references(() => table.id, { onDelete: 'cascade' })
|
||||||
|
.notNull(),
|
||||||
|
path: varchar('path').notNull(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasLocalizedManyTextField) {
|
||||||
|
columns.locale = adapter.enums.enum__locales('locale')
|
||||||
|
}
|
||||||
|
|
||||||
|
textsTable = pgTable(textsTableName, columns, (cols) => {
|
||||||
|
const indexes: Record<string, IndexBuilder> = {
|
||||||
|
orderParentIdx: index('order_parent_idx').on(cols.order, cols.parent),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasManyTextField === 'index') {
|
||||||
|
indexes.text_idx = index('text_idx').on(cols.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasLocalizedManyTextField) {
|
||||||
|
indexes.localeParent = index('locale_parent').on(cols.locale, cols.parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexes
|
||||||
|
})
|
||||||
|
|
||||||
|
adapter.tables[textsTableName] = textsTable
|
||||||
|
|
||||||
|
const textsTableRelations = relations(textsTable, ({ one }) => ({
|
||||||
|
parent: one(table, {
|
||||||
|
fields: [textsTable.parent],
|
||||||
|
references: [table.id],
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
adapter.relations[`relations_${textsTableName}`] = textsTableRelations
|
||||||
|
}
|
||||||
|
|
||||||
if (hasManyNumberField && buildNumbers) {
|
if (hasManyNumberField && buildNumbers) {
|
||||||
const numbersTableName = `${rootTableName}_numbers`
|
const numbersTableName = `${rootTableName}_numbers`
|
||||||
const columns: Record<string, PgColumnBuilder> = {
|
const columns: Record<string, PgColumnBuilder> = {
|
||||||
@@ -317,6 +363,9 @@ export const buildTable = ({
|
|||||||
result._locales = many(localesTable)
|
result._locales = many(localesTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasManyTextField) {
|
||||||
|
result._texts = many(textsTable)
|
||||||
|
}
|
||||||
if (hasManyNumberField) {
|
if (hasManyNumberField) {
|
||||||
result._numbers = many(numbersTable)
|
result._numbers = many(numbersTable)
|
||||||
}
|
}
|
||||||
@@ -332,5 +381,5 @@ export const buildTable = ({
|
|||||||
|
|
||||||
adapter.relations[`relations_${tableName}`] = tableRelations
|
adapter.relations[`relations_${tableName}`] = tableRelations
|
||||||
|
|
||||||
return { hasManyNumberField, relationsToBuild }
|
return { hasManyTextField, hasManyNumberField, relationsToBuild }
|
||||||
}
|
}
|
||||||
|
|||||||
17
packages/db-postgres/src/schema/getConfigIDType.ts
Normal file
17
packages/db-postgres/src/schema/getConfigIDType.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { type Field, fieldAffectsData } from 'payload/types'
|
||||||
|
|
||||||
|
export const getConfigIDType = (fields: Field[]): string => {
|
||||||
|
const idField = fields.find((field) => fieldAffectsData(field) && field.name === 'id')
|
||||||
|
|
||||||
|
if (idField) {
|
||||||
|
if (idField.type === 'number') {
|
||||||
|
return 'numeric'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idField.type === 'text') {
|
||||||
|
return 'varchar'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'integer'
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdent
|
|||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
adapter: PostgresAdapter
|
adapter: PostgresAdapter
|
||||||
|
buildTexts: boolean
|
||||||
buildNumbers: boolean
|
buildNumbers: boolean
|
||||||
buildRelationships: boolean
|
buildRelationships: boolean
|
||||||
columnPrefix?: string
|
columnPrefix?: string
|
||||||
@@ -55,13 +56,16 @@ type Args = {
|
|||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
hasLocalizedField: boolean
|
hasLocalizedField: boolean
|
||||||
|
hasLocalizedManyTextField: boolean
|
||||||
hasLocalizedManyNumberField: boolean
|
hasLocalizedManyNumberField: boolean
|
||||||
hasLocalizedRelationshipField: boolean
|
hasLocalizedRelationshipField: boolean
|
||||||
|
hasManyTextField: 'index' | boolean
|
||||||
hasManyNumberField: 'index' | boolean
|
hasManyNumberField: 'index' | boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const traverseFields = ({
|
export const traverseFields = ({
|
||||||
adapter,
|
adapter,
|
||||||
|
buildTexts,
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
columnPrefix,
|
columnPrefix,
|
||||||
@@ -84,6 +88,8 @@ export const traverseFields = ({
|
|||||||
}: Args): Result => {
|
}: Args): Result => {
|
||||||
let hasLocalizedField = false
|
let hasLocalizedField = false
|
||||||
let hasLocalizedRelationshipField = false
|
let hasLocalizedRelationshipField = false
|
||||||
|
let hasManyTextField: 'index' | boolean = false
|
||||||
|
let hasLocalizedManyTextField = false
|
||||||
let hasManyNumberField: 'index' | boolean = false
|
let hasManyNumberField: 'index' | boolean = false
|
||||||
let hasLocalizedManyNumberField = false
|
let hasLocalizedManyNumberField = false
|
||||||
|
|
||||||
@@ -135,7 +141,28 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'text':
|
case 'text': {
|
||||||
|
if (field.hasMany) {
|
||||||
|
if (field.localized) {
|
||||||
|
hasLocalizedManyTextField = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.index) {
|
||||||
|
hasManyTextField = 'index'
|
||||||
|
} else if (!hasManyTextField) {
|
||||||
|
hasManyTextField = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.unique) {
|
||||||
|
throw new InvalidConfiguration(
|
||||||
|
'Unique is not supported in Postgres for hasMany text fields.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetTable[fieldName] = varchar(columnName)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
case 'email':
|
case 'email':
|
||||||
case 'code':
|
case 'code':
|
||||||
case 'textarea': {
|
case 'textarea': {
|
||||||
@@ -286,21 +313,28 @@ export const traverseFields = ({
|
|||||||
baseExtraConfig._localeIdx = (cols) => index('_locale_idx').on(cols._locale)
|
baseExtraConfig._localeIdx = (cols) => index('_locale_idx').on(cols._locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasManyNumberField: subHasManyNumberField, relationsToBuild: subRelationsToBuild } =
|
const {
|
||||||
buildTable({
|
hasManyTextField: subHasManyTextField,
|
||||||
adapter,
|
hasManyNumberField: subHasManyNumberField,
|
||||||
baseColumns,
|
relationsToBuild: subRelationsToBuild,
|
||||||
baseExtraConfig,
|
} = buildTable({
|
||||||
disableNotNull: disableNotNullFromHere,
|
adapter,
|
||||||
disableUnique,
|
baseColumns,
|
||||||
fields: disableUnique ? idToUUID(field.fields) : field.fields,
|
baseExtraConfig,
|
||||||
rootRelationsToBuild,
|
disableNotNull: disableNotNullFromHere,
|
||||||
rootRelationships: relationships,
|
disableUnique,
|
||||||
rootTableIDColType,
|
fields: disableUnique ? idToUUID(field.fields) : field.fields,
|
||||||
rootTableName,
|
rootRelationsToBuild,
|
||||||
tableName: arrayTableName,
|
rootRelationships: relationships,
|
||||||
})
|
rootTableIDColType,
|
||||||
|
rootTableName,
|
||||||
|
tableName: arrayTableName,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (subHasManyTextField) {
|
||||||
|
if (!hasManyTextField || subHasManyTextField === 'index')
|
||||||
|
hasManyTextField = subHasManyTextField
|
||||||
|
}
|
||||||
if (subHasManyNumberField) {
|
if (subHasManyNumberField) {
|
||||||
if (!hasManyNumberField || subHasManyNumberField === 'index')
|
if (!hasManyNumberField || subHasManyNumberField === 'index')
|
||||||
hasManyNumberField = subHasManyNumberField
|
hasManyNumberField = subHasManyNumberField
|
||||||
@@ -361,6 +395,7 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
hasManyTextField: subHasManyTextField,
|
||||||
hasManyNumberField: subHasManyNumberField,
|
hasManyNumberField: subHasManyNumberField,
|
||||||
relationsToBuild: subRelationsToBuild,
|
relationsToBuild: subRelationsToBuild,
|
||||||
} = buildTable({
|
} = buildTable({
|
||||||
@@ -377,6 +412,11 @@ export const traverseFields = ({
|
|||||||
tableName: blockTableName,
|
tableName: blockTableName,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (subHasManyTextField) {
|
||||||
|
if (!hasManyTextField || subHasManyTextField === 'index')
|
||||||
|
hasManyTextField = subHasManyTextField
|
||||||
|
}
|
||||||
|
|
||||||
if (subHasManyNumberField) {
|
if (subHasManyNumberField) {
|
||||||
if (!hasManyNumberField || subHasManyNumberField === 'index')
|
if (!hasManyNumberField || subHasManyNumberField === 'index')
|
||||||
hasManyNumberField = subHasManyNumberField
|
hasManyNumberField = subHasManyNumberField
|
||||||
@@ -425,11 +465,14 @@ export const traverseFields = ({
|
|||||||
if (!('name' in field)) {
|
if (!('name' in field)) {
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: groupHasLocalizedField,
|
hasLocalizedField: groupHasLocalizedField,
|
||||||
|
hasLocalizedManyTextField: groupHasLocalizedManyTextField,
|
||||||
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
||||||
hasLocalizedRelationshipField: groupHasLocalizedRelationshipField,
|
hasLocalizedRelationshipField: groupHasLocalizedRelationshipField,
|
||||||
|
hasManyTextField: groupHasManyTextField,
|
||||||
hasManyNumberField: groupHasManyNumberField,
|
hasManyNumberField: groupHasManyNumberField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
|
buildTexts,
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
columnPrefix,
|
columnPrefix,
|
||||||
@@ -453,6 +496,8 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
if (groupHasLocalizedField) hasLocalizedField = true
|
if (groupHasLocalizedField) hasLocalizedField = true
|
||||||
if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
|
if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
|
||||||
|
if (groupHasManyTextField) hasManyTextField = true
|
||||||
|
if (groupHasLocalizedManyTextField) hasLocalizedManyTextField = true
|
||||||
if (groupHasManyNumberField) hasManyNumberField = true
|
if (groupHasManyNumberField) hasManyNumberField = true
|
||||||
if (groupHasLocalizedManyNumberField) hasLocalizedManyNumberField = true
|
if (groupHasLocalizedManyNumberField) hasLocalizedManyNumberField = true
|
||||||
break
|
break
|
||||||
@@ -462,11 +507,14 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: groupHasLocalizedField,
|
hasLocalizedField: groupHasLocalizedField,
|
||||||
|
hasLocalizedManyTextField: groupHasLocalizedManyTextField,
|
||||||
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
||||||
hasLocalizedRelationshipField: groupHasLocalizedRelationshipField,
|
hasLocalizedRelationshipField: groupHasLocalizedRelationshipField,
|
||||||
|
hasManyTextField: groupHasManyTextField,
|
||||||
hasManyNumberField: groupHasManyNumberField,
|
hasManyNumberField: groupHasManyNumberField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
|
buildTexts,
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
columnPrefix: `${columnName}_`,
|
columnPrefix: `${columnName}_`,
|
||||||
@@ -490,6 +538,8 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
if (groupHasLocalizedField) hasLocalizedField = true
|
if (groupHasLocalizedField) hasLocalizedField = true
|
||||||
if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
|
if (groupHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
|
||||||
|
if (groupHasManyTextField) hasManyTextField = true
|
||||||
|
if (groupHasLocalizedManyTextField) hasLocalizedManyTextField = true
|
||||||
if (groupHasManyNumberField) hasManyNumberField = true
|
if (groupHasManyNumberField) hasManyNumberField = true
|
||||||
if (groupHasLocalizedManyNumberField) hasLocalizedManyNumberField = true
|
if (groupHasLocalizedManyNumberField) hasLocalizedManyNumberField = true
|
||||||
break
|
break
|
||||||
@@ -500,11 +550,14 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: tabHasLocalizedField,
|
hasLocalizedField: tabHasLocalizedField,
|
||||||
|
hasLocalizedManyTextField: tabHasLocalizedManyTextField,
|
||||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
||||||
hasLocalizedRelationshipField: tabHasLocalizedRelationshipField,
|
hasLocalizedRelationshipField: tabHasLocalizedRelationshipField,
|
||||||
|
hasManyTextField: tabHasManyTextField,
|
||||||
hasManyNumberField: tabHasManyNumberField,
|
hasManyNumberField: tabHasManyNumberField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
|
buildTexts,
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
columnPrefix,
|
columnPrefix,
|
||||||
@@ -528,9 +581,10 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
if (tabHasLocalizedField) hasLocalizedField = true
|
if (tabHasLocalizedField) hasLocalizedField = true
|
||||||
if (tabHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
|
if (tabHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
|
||||||
|
if (tabHasManyTextField) hasManyTextField = true
|
||||||
|
if (tabHasLocalizedManyTextField) hasLocalizedManyTextField = true
|
||||||
if (tabHasManyNumberField) hasManyNumberField = true
|
if (tabHasManyNumberField) hasManyNumberField = true
|
||||||
if (tabHasLocalizedManyNumberField) hasLocalizedManyNumberField = true
|
if (tabHasLocalizedManyNumberField) hasLocalizedManyNumberField = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,11 +593,14 @@ 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,
|
||||||
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
||||||
|
hasManyTextField: rowHasManyTextField,
|
||||||
hasManyNumberField: rowHasManyNumberField,
|
hasManyNumberField: rowHasManyNumberField,
|
||||||
} = traverseFields({
|
} = traverseFields({
|
||||||
adapter,
|
adapter,
|
||||||
|
buildTexts,
|
||||||
buildNumbers,
|
buildNumbers,
|
||||||
buildRelationships,
|
buildRelationships,
|
||||||
columnPrefix,
|
columnPrefix,
|
||||||
@@ -567,6 +624,8 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
if (rowHasLocalizedField) hasLocalizedField = true
|
if (rowHasLocalizedField) hasLocalizedField = true
|
||||||
if (rowHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
|
if (rowHasLocalizedRelationshipField) hasLocalizedRelationshipField = true
|
||||||
|
if (rowHasManyTextField) hasManyTextField = true
|
||||||
|
if (rowHasLocalizedManyTextField) hasLocalizedManyTextField = true
|
||||||
if (rowHasManyNumberField) hasManyNumberField = true
|
if (rowHasManyNumberField) hasManyNumberField = true
|
||||||
if (rowHasLocalizedManyNumberField) hasLocalizedManyNumberField = true
|
if (rowHasLocalizedManyNumberField) hasLocalizedManyNumberField = true
|
||||||
break
|
break
|
||||||
@@ -604,8 +663,10 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
hasLocalizedField,
|
hasLocalizedField,
|
||||||
|
hasLocalizedManyTextField,
|
||||||
hasLocalizedManyNumberField,
|
hasLocalizedManyNumberField,
|
||||||
hasLocalizedRelationshipField,
|
hasLocalizedRelationshipField,
|
||||||
|
hasManyTextField,
|
||||||
hasManyNumberField,
|
hasManyNumberField,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Block } from 'payload/types'
|
import type { Block, Field } from 'payload/types'
|
||||||
|
|
||||||
import { InvalidConfiguration } from 'payload/errors'
|
import { InvalidConfiguration } from 'payload/errors'
|
||||||
import { flattenTopLevelFields } from 'payload/utilities'
|
import { fieldAffectsData, fieldHasSubFields, tabHasName } from 'payload/types'
|
||||||
|
|
||||||
import type { GenericTable } from '../types'
|
import type { GenericTable } from '../types'
|
||||||
|
|
||||||
@@ -12,6 +12,42 @@ type Args = {
|
|||||||
table: GenericTable
|
table: GenericTable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[] => {
|
||||||
|
return fields.reduce((fieldsToUse, field) => {
|
||||||
|
let fieldPrefix = prefix
|
||||||
|
|
||||||
|
if (field.type === 'blocks') {
|
||||||
|
return fieldsToUse
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldHasSubFields(field)) {
|
||||||
|
fieldPrefix = 'name' in field ? `${prefix}${field.name}.` : prefix
|
||||||
|
return [...fieldsToUse, ...getFlattenedFieldNames(field.fields, fieldPrefix)]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'tabs') {
|
||||||
|
return [
|
||||||
|
...fieldsToUse,
|
||||||
|
...field.tabs.reduce((tabFields, tab) => {
|
||||||
|
fieldPrefix = 'name' in tab ? `${prefix}.${tab.name}` : prefix
|
||||||
|
return [
|
||||||
|
...tabFields,
|
||||||
|
...(tabHasName(tab)
|
||||||
|
? [{ ...tab, type: 'tab' }]
|
||||||
|
: getFlattenedFieldNames(tab.fields, fieldPrefix)),
|
||||||
|
]
|
||||||
|
}, []),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldAffectsData(field)) {
|
||||||
|
return [...fieldsToUse, `${fieldPrefix?.replace('.', '_') || ''}${field.name}`]
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldsToUse
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
export const validateExistingBlockIsIdentical = ({
|
export const validateExistingBlockIsIdentical = ({
|
||||||
block,
|
block,
|
||||||
localized,
|
localized,
|
||||||
@@ -19,17 +55,23 @@ export const validateExistingBlockIsIdentical = ({
|
|||||||
table,
|
table,
|
||||||
}: Args): void => {
|
}: Args): void => {
|
||||||
if (table) {
|
if (table) {
|
||||||
const fieldNames = flattenTopLevelFields(block.fields).flatMap((field) => field.name)
|
const fieldNames = getFlattenedFieldNames(block.fields)
|
||||||
|
|
||||||
Object.keys(table).forEach((fieldName) => {
|
const missingField =
|
||||||
if (!['_locale', '_order', '_parentID', '_path', '_uuid'].includes(fieldName)) {
|
// ensure every field from the config is in the matching table
|
||||||
if (fieldNames.indexOf(fieldName) === -1) {
|
fieldNames.find((name) => Object.keys(table).indexOf(name) === -1) ||
|
||||||
throw new InvalidConfiguration(
|
// ensure every table column is matched for every field from the config
|
||||||
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${fieldName}, while the other block does not.`,
|
Object.keys(table).find((fieldName) => {
|
||||||
)
|
if (!['_locale', '_order', '_parentID', '_path', '_uuid'].includes(fieldName)) {
|
||||||
|
return fieldNames.indexOf(fieldName) === -1
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
|
if (missingField) {
|
||||||
|
throw new InvalidConfiguration(
|
||||||
|
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${missingField}, while the other block does not.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (Boolean(localized) !== Boolean(table._locale)) {
|
if (Boolean(localized) !== Boolean(table._locale)) {
|
||||||
throw new InvalidConfiguration(
|
throw new InvalidConfiguration(
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ export const beginTransaction: BeginTransaction = async function beginTransactio
|
|||||||
try {
|
try {
|
||||||
id = uuid()
|
id = uuid()
|
||||||
|
|
||||||
let reject: (value?: unknown) => void
|
let reject: () => Promise<void>
|
||||||
let resolve: (value?: unknown) => void
|
let resolve: () => Promise<void>
|
||||||
let transaction: DrizzleTransaction
|
let transaction: DrizzleTransaction
|
||||||
|
|
||||||
let transactionReady: (value?: unknown) => void
|
let transactionReady: () => void
|
||||||
|
|
||||||
// Drizzle only exposes a transactions API that is sufficient if you
|
// Drizzle only exposes a transactions API that is sufficient if you
|
||||||
// can directly pass around the `tx` argument. But our operations are spread
|
// can directly pass around the `tx` argument. But our operations are spread
|
||||||
@@ -24,13 +24,19 @@ export const beginTransaction: BeginTransaction = async function beginTransactio
|
|||||||
// and will call them in our respective transaction methods
|
// and will call them in our respective transaction methods
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.drizzle
|
const done = this.drizzle
|
||||||
.transaction(async (tx) => {
|
.transaction(async (tx) => {
|
||||||
transaction = tx
|
transaction = tx
|
||||||
await new Promise((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
|
resolve = () => {
|
||||||
|
res()
|
||||||
|
return done
|
||||||
|
}
|
||||||
|
reject = () => {
|
||||||
|
rej()
|
||||||
|
return done
|
||||||
|
}
|
||||||
transactionReady()
|
transactionReady()
|
||||||
resolve = res
|
|
||||||
reject = rej
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -39,7 +45,7 @@ export const beginTransaction: BeginTransaction = async function beginTransactio
|
|||||||
|
|
||||||
// Need to wait until the transaction is ready
|
// Need to wait until the transaction is ready
|
||||||
// before binding its `resolve` and `reject` methods below
|
// before binding its `resolve` and `reject` methods below
|
||||||
await new Promise((resolve) => (transactionReady = resolve))
|
await new Promise<void>((resolve) => (transactionReady = resolve))
|
||||||
|
|
||||||
this.sessions[id] = {
|
this.sessions[id] = {
|
||||||
db: transaction,
|
db: transaction,
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ export const commitTransaction: CommitTransaction = async function commitTransac
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.sessions[id].resolve()
|
await this.sessions[id].resolve()
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
this.sessions[id].reject()
|
await this.sessions[id].reject()
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this.sessions[id]
|
delete this.sessions[id]
|
||||||
|
|||||||
19
packages/db-postgres/src/transform/read/hasManyText.ts
Normal file
19
packages/db-postgres/src/transform/read/hasManyText.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import type { TextField } from 'payload/types'
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
field: TextField
|
||||||
|
locale?: string
|
||||||
|
textRows: Record<string, unknown>[]
|
||||||
|
ref: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transformHasManyText = ({ field, locale, textRows, ref }: Args) => {
|
||||||
|
const result = textRows.map(({ text }) => text)
|
||||||
|
|
||||||
|
if (locale) {
|
||||||
|
ref[field.name][locale] = result
|
||||||
|
} else {
|
||||||
|
ref[field.name] = result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ type TransformArgs = {
|
|||||||
// into the shape Payload expects based on field schema
|
// into the shape Payload expects based on field schema
|
||||||
export const transform = <T extends TypeWithID>({ config, data, fields }: TransformArgs): T => {
|
export const transform = <T extends TypeWithID>({ config, data, fields }: TransformArgs): T => {
|
||||||
let relationships: Record<string, Record<string, unknown>[]> = {}
|
let relationships: Record<string, Record<string, unknown>[]> = {}
|
||||||
|
let texts: Record<string, Record<string, unknown>[]> = {}
|
||||||
let numbers: Record<string, Record<string, unknown>[]> = {}
|
let numbers: Record<string, Record<string, unknown>[]> = {}
|
||||||
|
|
||||||
if ('_rels' in data) {
|
if ('_rels' in data) {
|
||||||
@@ -25,6 +26,11 @@ export const transform = <T extends TypeWithID>({ config, data, fields }: Transf
|
|||||||
delete data._rels
|
delete data._rels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('_texts' in data) {
|
||||||
|
texts = createPathMap(data._texts)
|
||||||
|
delete data._texts
|
||||||
|
}
|
||||||
|
|
||||||
if ('_numbers' in data) {
|
if ('_numbers' in data) {
|
||||||
numbers = createPathMap(data._numbers)
|
numbers = createPathMap(data._numbers)
|
||||||
delete data._numbers
|
delete data._numbers
|
||||||
@@ -42,6 +48,7 @@ export const transform = <T extends TypeWithID>({ config, data, fields }: Transf
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields,
|
fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path: '',
|
path: '',
|
||||||
relationships,
|
relationships,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { BlocksMap } from '../../utilities/createBlocksMap'
|
|||||||
|
|
||||||
import { transformHasManyNumber } from './hasManyNumber'
|
import { transformHasManyNumber } from './hasManyNumber'
|
||||||
import { transformRelationship } from './relationship'
|
import { transformRelationship } from './relationship'
|
||||||
|
import { transformHasManyText } from './hasManyText'
|
||||||
|
|
||||||
type TraverseFieldsArgs = {
|
type TraverseFieldsArgs = {
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +35,10 @@ type TraverseFieldsArgs = {
|
|||||||
* An array of Payload fields to traverse
|
* An array of Payload fields to traverse
|
||||||
*/
|
*/
|
||||||
fields: (Field | TabAsField)[]
|
fields: (Field | TabAsField)[]
|
||||||
|
/**
|
||||||
|
* All hasMany text fields, as returned by Drizzle, keyed on an object by field path
|
||||||
|
*/
|
||||||
|
texts: Record<string, Record<string, unknown>[]>
|
||||||
/**
|
/**
|
||||||
* All hasMany number fields, as returned by Drizzle, keyed on an object by field path
|
* All hasMany number fields, as returned by Drizzle, keyed on an object by field path
|
||||||
*/
|
*/
|
||||||
@@ -61,6 +66,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix,
|
fieldPrefix,
|
||||||
fields,
|
fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -77,6 +83,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix,
|
fieldPrefix,
|
||||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -96,6 +103,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix,
|
fieldPrefix,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -127,6 +135,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path: `${sanitizedPath}${field.name}.${row._order - 1}`,
|
path: `${sanitizedPath}${field.name}.${row._order - 1}`,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -151,6 +160,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path: `${sanitizedPath}${field.name}.${i}`,
|
path: `${sanitizedPath}${field.name}.${i}`,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -194,6 +204,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path: `${blockFieldPath}.${row._order - 1}`,
|
path: `${blockFieldPath}.${row._order - 1}`,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -224,6 +235,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path: `${blockFieldPath}.${i}`,
|
path: `${blockFieldPath}.${i}`,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -285,6 +297,40 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field.type === 'text' && field?.hasMany) {
|
||||||
|
const textPathMatch = texts[`${sanitizedPath}${field.name}`]
|
||||||
|
if (!textPathMatch) return result
|
||||||
|
|
||||||
|
if (field.localized) {
|
||||||
|
result[field.name] = {}
|
||||||
|
const textsByLocale: Record<string, Record<string, unknown>[]> = {}
|
||||||
|
|
||||||
|
textPathMatch.forEach((row) => {
|
||||||
|
if (typeof row.locale === 'string') {
|
||||||
|
if (!textsByLocale[row.locale]) textsByLocale[row.locale] = []
|
||||||
|
textsByLocale[row.locale].push(row)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.entries(textsByLocale).forEach(([locale, texts]) => {
|
||||||
|
transformHasManyText({
|
||||||
|
field,
|
||||||
|
locale,
|
||||||
|
textRows: texts,
|
||||||
|
ref: result,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
transformHasManyText({
|
||||||
|
field,
|
||||||
|
textRows: textPathMatch,
|
||||||
|
ref: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
if (field.type === 'number' && field.hasMany) {
|
if (field.type === 'number' && field.hasMany) {
|
||||||
const numberPathMatch = numbers[`${sanitizedPath}${field.name}`]
|
const numberPathMatch = numbers[`${sanitizedPath}${field.name}`]
|
||||||
if (!numberPathMatch) return result
|
if (!numberPathMatch) return result
|
||||||
@@ -374,6 +420,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix: groupFieldPrefix,
|
fieldPrefix: groupFieldPrefix,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path: `${sanitizedPath}${field.name}`,
|
path: `${sanitizedPath}${field.name}`,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -390,6 +437,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix: groupFieldPrefix,
|
fieldPrefix: groupFieldPrefix,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path: `${sanitizedPath}${field.name}`,
|
path: `${sanitizedPath}${field.name}`,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -400,6 +448,21 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'text': {
|
||||||
|
let val = fieldData
|
||||||
|
if (typeof fieldData === 'string') {
|
||||||
|
val = String(fieldData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof locale === 'string') {
|
||||||
|
ref[locale] = val
|
||||||
|
} else {
|
||||||
|
result[field.name] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case 'number': {
|
case 'number': {
|
||||||
let val = fieldData
|
let val = fieldData
|
||||||
if (typeof fieldData === 'string') {
|
if (typeof fieldData === 'string') {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type Args = {
|
|||||||
data: unknown
|
data: unknown
|
||||||
field: ArrayField
|
field: ArrayField
|
||||||
locale?: string
|
locale?: string
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
path: string
|
path: string
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
@@ -36,6 +37,7 @@ export const transformArray = ({
|
|||||||
data,
|
data,
|
||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -86,6 +88,7 @@ export const transformArray = ({
|
|||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locales: newRow.locales,
|
locales: newRow.locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName: arrayTableName,
|
parentTableName: arrayTableName,
|
||||||
path: `${path || ''}${field.name}.${i}.`,
|
path: `${path || ''}${field.name}.${i}.`,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type Args = {
|
|||||||
data: Record<string, unknown>[]
|
data: Record<string, unknown>[]
|
||||||
field: BlockField
|
field: BlockField
|
||||||
locale?: string
|
locale?: string
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
path: string
|
path: string
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
@@ -34,6 +35,7 @@ export const transformBlocks = ({
|
|||||||
data,
|
data,
|
||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -84,6 +86,7 @@ export const transformBlocks = ({
|
|||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields: matchedBlock.fields,
|
fields: matchedBlock.fields,
|
||||||
locales: newRow.locales,
|
locales: newRow.locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName: blockTableName,
|
parentTableName: blockTableName,
|
||||||
path: `${path || ''}${field.name}.${i}.`,
|
path: `${path || ''}${field.name}.${i}.`,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export const transformForWrite = ({
|
|||||||
blocks: {},
|
blocks: {},
|
||||||
blocksToDelete: new Set(),
|
blocksToDelete: new Set(),
|
||||||
locales: {},
|
locales: {},
|
||||||
|
texts: [],
|
||||||
numbers: [],
|
numbers: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
relationshipsToDelete: [],
|
relationshipsToDelete: [],
|
||||||
@@ -47,6 +48,7 @@ export const transformForWrite = ({
|
|||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields,
|
fields,
|
||||||
locales: rowToInsert.locales,
|
locales: rowToInsert.locales,
|
||||||
|
texts: rowToInsert.texts,
|
||||||
numbers: rowToInsert.numbers,
|
numbers: rowToInsert.numbers,
|
||||||
parentTableName: tableName,
|
parentTableName: tableName,
|
||||||
path,
|
path,
|
||||||
|
|||||||
15
packages/db-postgres/src/transform/write/texts.ts
Normal file
15
packages/db-postgres/src/transform/write/texts.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
type Args = {
|
||||||
|
baseRow: Record<string, unknown>
|
||||||
|
data: unknown[]
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transformTexts = ({ baseRow, data, texts }: Args) => {
|
||||||
|
data.forEach((val, i) => {
|
||||||
|
texts.push({
|
||||||
|
...baseRow,
|
||||||
|
text: val,
|
||||||
|
order: i + 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { transformBlocks } from './blocks'
|
|||||||
import { transformNumbers } from './numbers'
|
import { transformNumbers } from './numbers'
|
||||||
import { transformRelationship } from './relationships'
|
import { transformRelationship } from './relationships'
|
||||||
import { transformSelects } from './selects'
|
import { transformSelects } from './selects'
|
||||||
|
import { transformTexts } from './texts'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
adapter: PostgresAdapter
|
adapter: PostgresAdapter
|
||||||
@@ -44,6 +45,7 @@ type Args = {
|
|||||||
locales: {
|
locales: {
|
||||||
[locale: string]: Record<string, unknown>
|
[locale: string]: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
/**
|
/**
|
||||||
* This is the name of the parent table
|
* This is the name of the parent table
|
||||||
@@ -71,6 +73,7 @@ export const traverseFields = ({
|
|||||||
fields,
|
fields,
|
||||||
forcedLocale,
|
forcedLocale,
|
||||||
locales,
|
locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path,
|
path,
|
||||||
@@ -108,6 +111,7 @@ export const traverseFields = ({
|
|||||||
data: localeData,
|
data: localeData,
|
||||||
field,
|
field,
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -128,6 +132,7 @@ export const traverseFields = ({
|
|||||||
blocksToDelete,
|
blocksToDelete,
|
||||||
data: data[field.name],
|
data: data[field.name],
|
||||||
field,
|
field,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -158,6 +163,7 @@ export const traverseFields = ({
|
|||||||
data: localeData,
|
data: localeData,
|
||||||
field,
|
field,
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -175,6 +181,7 @@ export const traverseFields = ({
|
|||||||
blocksToDelete,
|
blocksToDelete,
|
||||||
data: fieldData,
|
data: fieldData,
|
||||||
field,
|
field,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
@@ -203,6 +210,7 @@ export const traverseFields = ({
|
|||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
forcedLocale: localeKey,
|
forcedLocale: localeKey,
|
||||||
locales,
|
locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${field.name}.`,
|
path: `${path || ''}${field.name}.`,
|
||||||
@@ -225,6 +233,7 @@ export const traverseFields = ({
|
|||||||
fieldPrefix: `${fieldName}_`,
|
fieldPrefix: `${fieldName}_`,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locales,
|
locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${field.name}.`,
|
path: `${path || ''}${field.name}.`,
|
||||||
@@ -258,6 +267,7 @@ export const traverseFields = ({
|
|||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
forcedLocale: localeKey,
|
forcedLocale: localeKey,
|
||||||
locales,
|
locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${tab.name}.`,
|
path: `${path || ''}${tab.name}.`,
|
||||||
@@ -280,6 +290,7 @@ export const traverseFields = ({
|
|||||||
fieldPrefix: `${fieldPrefix || ''}${tab.name}_`,
|
fieldPrefix: `${fieldPrefix || ''}${tab.name}_`,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
locales,
|
locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${tab.name}.`,
|
path: `${path || ''}${tab.name}.`,
|
||||||
@@ -303,6 +314,7 @@ export const traverseFields = ({
|
|||||||
fieldPrefix,
|
fieldPrefix,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
locales,
|
locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path,
|
path,
|
||||||
@@ -328,6 +340,7 @@ export const traverseFields = ({
|
|||||||
fieldPrefix,
|
fieldPrefix,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locales,
|
locales,
|
||||||
|
texts,
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path,
|
path,
|
||||||
@@ -382,6 +395,37 @@ export const traverseFields = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field.type === 'text' && field.hasMany) {
|
||||||
|
const textPath = `${path || ''}${field.name}`
|
||||||
|
|
||||||
|
if (field.localized) {
|
||||||
|
if (typeof fieldData === 'object') {
|
||||||
|
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||||
|
if (Array.isArray(localeData)) {
|
||||||
|
transformTexts({
|
||||||
|
baseRow: {
|
||||||
|
locale: localeKey,
|
||||||
|
path: textPath,
|
||||||
|
},
|
||||||
|
data: localeData,
|
||||||
|
texts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(fieldData)) {
|
||||||
|
transformTexts({
|
||||||
|
baseRow: {
|
||||||
|
path: textPath,
|
||||||
|
},
|
||||||
|
data: fieldData,
|
||||||
|
texts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (field.type === 'number' && field.hasMany) {
|
if (field.type === 'number' && field.hasMany) {
|
||||||
const numberPath = `${path || ''}${field.name}`
|
const numberPath = `${path || ''}${field.name}`
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type RowToInsert = {
|
|||||||
locales: {
|
locales: {
|
||||||
[locale: string]: Record<string, unknown>
|
[locale: string]: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
relationshipsToDelete: RelationshipToDelete[]
|
relationshipsToDelete: RelationshipToDelete[]
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
|||||||
sessions: {
|
sessions: {
|
||||||
[id: string]: {
|
[id: string]: {
|
||||||
db: DrizzleTransaction
|
db: DrizzleTransaction
|
||||||
reject: () => void
|
reject: () => Promise<void>
|
||||||
resolve: () => void
|
resolve: () => Promise<void>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tables: Record<string, GenericTable>
|
tables: Record<string, GenericTable>
|
||||||
@@ -86,8 +86,8 @@ declare module 'payload' {
|
|||||||
sessions: {
|
sessions: {
|
||||||
[id: string]: {
|
[id: string]: {
|
||||||
db: DrizzleTransaction
|
db: DrizzleTransaction
|
||||||
reject: () => void
|
reject: () => Promise<void>
|
||||||
resolve: () => void
|
resolve: () => Promise<void>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tables: Record<string, GenericTable>
|
tables: Record<string, GenericTable>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export const upsertRow = async <T extends TypeWithID>({
|
|||||||
|
|
||||||
const localesToInsert: Record<string, unknown>[] = []
|
const localesToInsert: Record<string, unknown>[] = []
|
||||||
const relationsToInsert: Record<string, unknown>[] = []
|
const relationsToInsert: Record<string, unknown>[] = []
|
||||||
|
const textsToInsert: Record<string, unknown>[] = []
|
||||||
const numbersToInsert: Record<string, unknown>[] = []
|
const numbersToInsert: Record<string, unknown>[] = []
|
||||||
const blocksToInsert: { [blockType: string]: BlockRowToInsert[] } = {}
|
const blocksToInsert: { [blockType: string]: BlockRowToInsert[] } = {}
|
||||||
const selectsToInsert: { [selectTableName: string]: Record<string, unknown>[] } = {}
|
const selectsToInsert: { [selectTableName: string]: Record<string, unknown>[] } = {}
|
||||||
@@ -89,6 +90,14 @@ export const upsertRow = async <T extends TypeWithID>({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there are texts, add parent to each
|
||||||
|
if (rowToInsert.texts.length > 0) {
|
||||||
|
rowToInsert.texts.forEach((textRow) => {
|
||||||
|
textRow.parent = insertedRow.id
|
||||||
|
textsToInsert.push(textRow)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// If there are numbers, add parent to each
|
// If there are numbers, add parent to each
|
||||||
if (rowToInsert.numbers.length > 0) {
|
if (rowToInsert.numbers.length > 0) {
|
||||||
rowToInsert.numbers.forEach((numberRow) => {
|
rowToInsert.numbers.forEach((numberRow) => {
|
||||||
@@ -161,6 +170,29 @@ export const upsertRow = async <T extends TypeWithID>({
|
|||||||
await db.insert(adapter.tables[relationshipsTableName]).values(relationsToInsert)
|
await db.insert(adapter.tables[relationshipsTableName]).values(relationsToInsert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// //////////////////////////////////
|
||||||
|
// INSERT hasMany TEXTS
|
||||||
|
// //////////////////////////////////
|
||||||
|
|
||||||
|
const textsTableName = `${tableName}_texts`
|
||||||
|
|
||||||
|
if (operation === 'update') {
|
||||||
|
await deleteExistingRowsByPath({
|
||||||
|
adapter,
|
||||||
|
db,
|
||||||
|
localeColumnName: 'locale',
|
||||||
|
parentColumnName: 'parent',
|
||||||
|
parentID: insertedRow.id,
|
||||||
|
pathColumnName: 'path',
|
||||||
|
rows: textsToInsert,
|
||||||
|
tableName: textsTableName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textsToInsert.length > 0) {
|
||||||
|
await db.insert(adapter.tables[textsTableName]).values(textsToInsert).returning()
|
||||||
|
}
|
||||||
|
|
||||||
// //////////////////////////////////
|
// //////////////////////////////////
|
||||||
// INSERT hasMany NUMBERS
|
// INSERT hasMany NUMBERS
|
||||||
// //////////////////////////////////
|
// //////////////////////////////////
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ module.exports = {
|
|||||||
'partition-by-comment': true,
|
'partition-by-comment': true,
|
||||||
groups: ['top', 'unknown'],
|
groups: ['top', 'unknown'],
|
||||||
'custom-groups': {
|
'custom-groups': {
|
||||||
top: ['_id', 'id', 'name'],
|
top: ['_id', 'id', 'name', 'slug', 'type'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/eslint-config",
|
"name": "@payloadcms/eslint-config",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"description": "Payload styles for ESLint and Prettier",
|
"description": "Payload styles for ESLint and Prettier",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/live-preview",
|
"name": "@payloadcms/live-preview",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"description": "The official live preview JavaScript SDK for Payload",
|
"description": "The official live preview JavaScript SDK for Payload",
|
||||||
"repository": "https://github.com/payloadcms/payload",
|
"repository": "https://github.com/payloadcms/payload",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
"version": "2.5.0",
|
"version": "2.7.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",
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
"minimist": "1.2.8",
|
"minimist": "1.2.8",
|
||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.38.0",
|
"monaco-editor": "0.38.0",
|
||||||
"nodemailer": "6.9.4",
|
"nodemailer": "6.9.8",
|
||||||
"object-to-formdata": "4.5.1",
|
"object-to-formdata": "4.5.1",
|
||||||
"passport": "0.6.0",
|
"passport": "0.6.0",
|
||||||
"passport-anonymous": "1.0.1",
|
"passport-anonymous": "1.0.1",
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
"@types/minimist": "1.2.2",
|
"@types/minimist": "1.2.2",
|
||||||
"@types/mkdirp": "1.0.2",
|
"@types/mkdirp": "1.0.2",
|
||||||
"@types/node-fetch": "2.6.4",
|
"@types/node-fetch": "2.6.4",
|
||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.14",
|
||||||
"@types/passport": "1.0.12",
|
"@types/passport": "1.0.12",
|
||||||
"@types/passport-anonymous": "1.0.3",
|
"@types/passport-anonymous": "1.0.3",
|
||||||
"@types/passport-jwt": "3.0.9",
|
"@types/passport-jwt": "3.0.9",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { filterFields } from '../../forms/RenderFields/filterFields'
|
|||||||
import { Gutter } from '../Gutter'
|
import { Gutter } from '../Gutter'
|
||||||
import ViewDescription from '../ViewDescription'
|
import ViewDescription from '../ViewDescription'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
import { useOperation } from '../../utilities/OperationProvider'
|
||||||
|
|
||||||
const baseClass = 'document-fields'
|
const baseClass = 'document-fields'
|
||||||
|
|
||||||
@@ -34,12 +35,15 @@ export const DocumentFields: React.FC<{
|
|||||||
permissions,
|
permissions,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
|
const operation = useOperation()
|
||||||
|
|
||||||
const sidebarFields = filterFields({
|
const sidebarFields = filterFields({
|
||||||
fieldSchema: fields,
|
fieldSchema: fields,
|
||||||
fieldTypes,
|
fieldTypes,
|
||||||
filter: (field) => field?.admin?.position === 'sidebar',
|
filter: (field) => field?.admin?.position === 'sidebar',
|
||||||
permissions: permissions.fields,
|
permissions: permissions.fields,
|
||||||
readOnly: !hasSavePermission,
|
readOnly: !hasSavePermission,
|
||||||
|
operation,
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasSidebarFields = sidebarFields && sidebarFields.length > 0
|
const hasSidebarFields = sidebarFields && sidebarFields.length > 0
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ export const DocumentTab: React.FC<DocumentTabProps & DocumentTabConfig> = (prop
|
|||||||
? checkIsActive
|
? checkIsActive
|
||||||
: location.pathname.startsWith(href)
|
: location.pathname.startsWith(href)
|
||||||
|
|
||||||
if (!condition || (condition && condition({ collection, config, documentInfo, global }))) {
|
if (
|
||||||
|
!condition ||
|
||||||
|
(condition && Boolean(condition({ collection, config, documentInfo, global })))
|
||||||
|
) {
|
||||||
const labelToRender = typeof label === 'function' ? label({ t }) : label
|
const labelToRender = typeof label === 'function' ? label({ t }) : label
|
||||||
const pillToRender = typeof pillLabel === 'function' ? pillLabel({ versions }) : pillLabel
|
const pillToRender = typeof pillLabel === 'function' ? pillLabel({ versions }) : pillLabel
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ $header-height: base(5);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[dir='rtl']
|
||||||
|
&__actions {
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: unset;
|
||||||
|
}
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
// place the localizer outside the `overflow: hidden` container so that the popup is visible
|
// place the localizer outside the `overflow: hidden` container so that the popup is visible
|
||||||
// this means we need to use a placeholder div so that the space is retained in the DOM
|
// this means we need to use a placeholder div so that the space is retained in the DOM
|
||||||
|
[dir='rtl'] &__localizer {
|
||||||
|
right: unset;
|
||||||
|
left: base(4.5)
|
||||||
|
}
|
||||||
&__localizer {
|
&__localizer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
|||||||
const moreThanOneAvailableCollection = enabledCollectionConfigs.length > 1
|
const moreThanOneAvailableCollection = enabledCollectionConfigs.length > 1
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { admin: { listSearchableFields } = {}, slug } = selectedCollectionConfig
|
const { slug, admin: { listSearchableFields } = {} } = selectedCollectionConfig
|
||||||
const params: {
|
const params: {
|
||||||
cacheBust?: number
|
cacheBust?: number
|
||||||
limit?: number
|
limit?: number
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
|
[dir='rtl']
|
||||||
|
&__text-align--left {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
&__text-align--left {
|
&__text-align--left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@@ -14,11 +17,15 @@
|
|||||||
&__text-align--center {
|
&__text-align--center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
[dir='rtl']
|
||||||
|
&__text-align--right {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
&__text-align--right {
|
&__text-align--right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
&__button {
|
&__button {
|
||||||
@extend %btn-reset;
|
@extend %btn-reset;
|
||||||
padding-left: var(--list-button-padding);
|
padding-left: var(--list-button-padding);
|
||||||
|
|||||||
@@ -80,13 +80,18 @@
|
|||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// HORIZONTAL ALIGNMENT
|
// HORIZONTAL ALIGNMENT
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
|
[dir='rtl']
|
||||||
|
&--h-align-left {
|
||||||
|
.popup__caret {
|
||||||
|
right: var(--popup-padding);
|
||||||
|
left: unset
|
||||||
|
}
|
||||||
|
}
|
||||||
&--h-align-left {
|
&--h-align-left {
|
||||||
.popup__caret {
|
.popup__caret {
|
||||||
left: var(--popup-padding);
|
left: var(--popup-padding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--h-align-center {
|
&--h-align-center {
|
||||||
.popup__content {
|
.popup__content {
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -99,6 +104,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[dir='rtl']
|
||||||
|
&--h-align-right {
|
||||||
|
.popup__content {
|
||||||
|
right: unset;
|
||||||
|
left:0
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup__caret {
|
||||||
|
right: unset;
|
||||||
|
left: var(--popup-padding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&--h-align-right {
|
&--h-align-right {
|
||||||
.popup__content {
|
.popup__content {
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const RelationshipField: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setErrorLoading(t('errors:unspecific'))
|
setErrorLoading(t('error:unspecific'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, Promise.resolve())
|
}, Promise.resolve())
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const iterateFields = async ({
|
|||||||
if (!fieldIsPresentationalOnly(field) && !field?.admin?.disabled) {
|
if (!fieldIsPresentationalOnly(field) && !field?.admin?.disabled) {
|
||||||
const passesCondition = Boolean(
|
const passesCondition = Boolean(
|
||||||
(field?.admin?.condition
|
(field?.admin?.condition
|
||||||
? field.admin.condition(fullData || {}, initialData || {}, { user })
|
? Boolean(field.admin.condition(fullData || {}, initialData || {}, { user }))
|
||||||
: true) && parentPassesCondition,
|
: true) && parentPassesCondition,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
|
|||||||
// Besides those who still fail their own conditions
|
// Besides those who still fail their own conditions
|
||||||
|
|
||||||
if (passesCondition && field.condition) {
|
if (passesCondition && field.condition) {
|
||||||
passesCondition = field.condition(
|
passesCondition = Boolean(
|
||||||
reduceFieldsToValues(state, true),
|
field.condition(reduceFieldsToValues(state, true), getSiblingData(state, path), {
|
||||||
getSiblingData(state, path),
|
user,
|
||||||
{ user },
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
& > .field-type {
|
& > .field-type {
|
||||||
margin-bottom: var(--spacing-field);
|
margin-bottom: var(--spacing-field);
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
&[type='hidden'] {
|
&[type='hidden'] {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|||||||
@@ -56,12 +56,11 @@ const JSONField: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(val) => {
|
(val) => {
|
||||||
if (readOnly) return
|
|
||||||
console.log(val)
|
|
||||||
setStringValue(val)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setValue(JSON.parse(val))
|
if (readOnly) return
|
||||||
|
setStringValue(val)
|
||||||
|
|
||||||
|
setValue(val ? JSON.parse(val) : '')
|
||||||
setJsonError(undefined)
|
setJsonError(undefined)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setJsonError(e)
|
setJsonError(e)
|
||||||
@@ -71,10 +70,18 @@ const JSONField: React.FC<Props> = (props) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasLoadedValue) return
|
try {
|
||||||
setStringValue(JSON.stringify(value ? value : initialValue, null, 2))
|
const hasValue = value && value.toString().length > 0
|
||||||
setHasLoadedValue(true)
|
if (hasLoadedValue) {
|
||||||
}, [initialValue, value])
|
setStringValue(hasValue ? JSON.stringify(value, null, 2) : '')
|
||||||
|
} else {
|
||||||
|
setStringValue(JSON.stringify(hasValue ? value : initialValue, null, 2))
|
||||||
|
setHasLoadedValue(true)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setJsonError(e)
|
||||||
|
}
|
||||||
|
}, [initialValue, value, hasLoadedValue])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import React from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import type { TextField } from '../../../../../fields/config/types'
|
import type { TextField } from '../../../../../fields/config/types'
|
||||||
|
import type { Option } from '../../../elements/ReactSelect/types'
|
||||||
import type { Description } from '../../FieldDescription/types'
|
import type { Description } from '../../FieldDescription/types'
|
||||||
|
|
||||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||||
|
import ReactSelect from '../../../elements/ReactSelect'
|
||||||
import DefaultError from '../../Error'
|
import DefaultError from '../../Error'
|
||||||
import FieldDescription from '../../FieldDescription'
|
import FieldDescription from '../../FieldDescription'
|
||||||
import DefaultLabel from '../../Label'
|
import DefaultLabel from '../../Label'
|
||||||
@@ -21,6 +23,7 @@ export type TextInputProps = Omit<TextField, 'type'> & {
|
|||||||
className?: string
|
className?: string
|
||||||
description?: Description
|
description?: Description
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
|
hasMany?: boolean
|
||||||
inputRef?: React.MutableRefObject<HTMLInputElement>
|
inputRef?: React.MutableRefObject<HTMLInputElement>
|
||||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
|
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
|
||||||
@@ -32,6 +35,7 @@ export type TextInputProps = Omit<TextField, 'type'> & {
|
|||||||
showError?: boolean
|
showError?: boolean
|
||||||
style?: React.CSSProperties
|
style?: React.CSSProperties
|
||||||
value?: string
|
value?: string
|
||||||
|
valueToRender?: Option[]
|
||||||
width?: string
|
width?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +48,11 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
className,
|
className,
|
||||||
description,
|
description,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
|
hasMany,
|
||||||
inputRef,
|
inputRef,
|
||||||
label,
|
label,
|
||||||
|
maxRows,
|
||||||
|
minRows,
|
||||||
onChange,
|
onChange,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
path,
|
path,
|
||||||
@@ -56,17 +63,25 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
showError,
|
showError,
|
||||||
style,
|
style,
|
||||||
value,
|
value,
|
||||||
|
valueToRender,
|
||||||
width,
|
width,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const { i18n } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
|
|
||||||
const ErrorComp = Error || DefaultError
|
const ErrorComp = Error || DefaultError
|
||||||
const LabelComp = Label || DefaultLabel
|
const LabelComp = Label || DefaultLabel
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={[fieldBaseClass, 'text', className, showError && 'error', readOnly && 'read-only']
|
className={[
|
||||||
|
fieldBaseClass,
|
||||||
|
'text',
|
||||||
|
className,
|
||||||
|
showError && 'error',
|
||||||
|
readOnly && 'read-only',
|
||||||
|
hasMany && 'has-many',
|
||||||
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')}
|
.join(' ')}
|
||||||
style={{
|
style={{
|
||||||
@@ -78,18 +93,45 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||||
<div className="input-wrapper">
|
<div className="input-wrapper">
|
||||||
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
|
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
|
||||||
<input
|
{hasMany ? (
|
||||||
data-rtl={rtl}
|
<ReactSelect
|
||||||
disabled={readOnly}
|
className={`field-${path.replace(/\./g, '__')}`}
|
||||||
id={`field-${path.replace(/\./g, '__')}`}
|
disabled={readOnly}
|
||||||
name={path}
|
filterOption={(option, rawInput) => {
|
||||||
onChange={onChange}
|
const isOverHasMany = Array.isArray(value) && value.length >= maxRows
|
||||||
onKeyDown={onKeyDown}
|
return !isOverHasMany
|
||||||
placeholder={getTranslation(placeholder, i18n)}
|
}}
|
||||||
ref={inputRef}
|
isClearable
|
||||||
type="text"
|
isCreatable
|
||||||
value={value || ''}
|
isMulti
|
||||||
/>
|
isSortable
|
||||||
|
noOptionsMessage={({ inputValue }) => {
|
||||||
|
const isOverHasMany = Array.isArray(value) && value.length >= maxRows
|
||||||
|
if (isOverHasMany) {
|
||||||
|
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
|
||||||
|
}
|
||||||
|
return t('general:noOptions')
|
||||||
|
}}
|
||||||
|
onChange={onChange}
|
||||||
|
options={[]}
|
||||||
|
placeholder={t('general:enterAValue')}
|
||||||
|
showError={showError}
|
||||||
|
value={valueToRender}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
data-rtl={rtl}
|
||||||
|
disabled={readOnly}
|
||||||
|
id={`field-${path.replace(/\./g, '__')}`}
|
||||||
|
name={path}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
placeholder={getTranslation(placeholder, i18n)}
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
value={value || ''}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
|
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
|
||||||
</div>
|
</div>
|
||||||
<FieldDescription
|
<FieldDescription
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
.field-type.text {
|
.field-type.text {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
input {
|
&:not(.has-many) {
|
||||||
@include formInput;
|
input {
|
||||||
|
@include formInput;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import type { Props } from './types'
|
import type { Props } from './types'
|
||||||
|
|
||||||
@@ -24,11 +24,14 @@ const Text: React.FC<Props> = (props) => {
|
|||||||
style,
|
style,
|
||||||
width,
|
width,
|
||||||
} = {},
|
} = {},
|
||||||
|
hasMany,
|
||||||
inputRef,
|
inputRef,
|
||||||
label,
|
label,
|
||||||
localized,
|
localized,
|
||||||
maxLength,
|
maxLength,
|
||||||
|
maxRows,
|
||||||
minLength,
|
minLength,
|
||||||
|
minRows,
|
||||||
path: pathFromProps,
|
path: pathFromProps,
|
||||||
required,
|
required,
|
||||||
validate = text,
|
validate = text,
|
||||||
@@ -58,6 +61,50 @@ const Text: React.FC<Props> = (props) => {
|
|||||||
validate: memoizedValidate,
|
validate: memoizedValidate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleOnChange = (e) => {
|
||||||
|
setValue(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleHasManyChange = useCallback(
|
||||||
|
(selectedOption) => {
|
||||||
|
if (!readOnly) {
|
||||||
|
let newValue
|
||||||
|
if (!selectedOption) {
|
||||||
|
newValue = []
|
||||||
|
} else if (Array.isArray(selectedOption)) {
|
||||||
|
newValue = selectedOption.map((option) => option.value?.value || option.value)
|
||||||
|
} else {
|
||||||
|
newValue = [selectedOption.value?.value || selectedOption.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(newValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[readOnly, setValue],
|
||||||
|
)
|
||||||
|
|
||||||
|
const [valueToRender, setValueToRender] = useState<
|
||||||
|
{ id: string; label: string; value: { value: string } }[]
|
||||||
|
>([]) // Only for hasMany
|
||||||
|
|
||||||
|
// useeffect update valueToRender:
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasMany && Array.isArray(value)) {
|
||||||
|
setValueToRender(
|
||||||
|
value.map((val, index) => {
|
||||||
|
return {
|
||||||
|
id: `${val}${index}`, // append index to avoid duplicate keys but allow duplicate numbers
|
||||||
|
label: `${val}`,
|
||||||
|
value: {
|
||||||
|
toString: () => `${val}${index}`,
|
||||||
|
value: val?.value || val,
|
||||||
|
}, // You're probably wondering, why the hell is this done that way? Well, React-select automatically uses "label-value" as a key, so we will get that react duplicate key warning if we just pass in the value as multiple values can be the same. So we need to append the index to the toString() of the value to avoid that warning, as it uses that as the key.
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [value, hasMany])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
Error={Error}
|
Error={Error}
|
||||||
@@ -67,12 +114,13 @@ const Text: React.FC<Props> = (props) => {
|
|||||||
className={className}
|
className={className}
|
||||||
description={description}
|
description={description}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
|
hasMany={hasMany}
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
label={label}
|
label={label}
|
||||||
|
maxRows={maxRows}
|
||||||
|
minRows={minRows}
|
||||||
name={name}
|
name={name}
|
||||||
onChange={(e) => {
|
onChange={hasMany ? handleHasManyChange : handleOnChange}
|
||||||
setValue(e.target.value)
|
|
||||||
}}
|
|
||||||
path={path}
|
path={path}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
@@ -81,6 +129,7 @@ const Text: React.FC<Props> = (props) => {
|
|||||||
showError={showError}
|
showError={showError}
|
||||||
style={style}
|
style={style}
|
||||||
value={value}
|
value={value}
|
||||||
|
valueToRender={valueToRender}
|
||||||
width={width}
|
width={width}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const WatchCondition: React.FC<Props> = ({
|
|||||||
data.id = id
|
data.id = id
|
||||||
|
|
||||||
const hasCondition = Boolean(condition)
|
const hasCondition = Boolean(condition)
|
||||||
const isPassingCondition = hasCondition ? condition(data, siblingData, { user }) : true
|
const isPassingCondition = hasCondition ? Boolean(condition(data, siblingData, { user })) : true
|
||||||
const field = fields[path]
|
const field = fields[path]
|
||||||
|
|
||||||
const wasPassingCondition = field?.passesCondition
|
const wasPassingCondition = field?.passesCondition
|
||||||
|
|||||||
@@ -17,9 +17,16 @@ const Component: React.FC<{
|
|||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
onConfirm: () => void
|
onConfirm: () => void
|
||||||
}> = ({ isActive, onCancel, onConfirm }) => {
|
}> = ({ isActive, onCancel, onConfirm }) => {
|
||||||
const { closeModal, openModal } = useModal()
|
const { closeModal, openModal, modalState } = useModal()
|
||||||
const { t } = useTranslation('general')
|
const { t } = useTranslation('general')
|
||||||
|
|
||||||
|
// Manually check for modal state as 'esc' key will not trigger the nav inactivity
|
||||||
|
useEffect(() => {
|
||||||
|
if (!modalState?.[modalSlug]?.isOpen && isActive) {
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
}, [modalState])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isActive) openModal(modalSlug)
|
if (isActive) openModal(modalSlug)
|
||||||
else closeModal(modalSlug)
|
else closeModal(modalSlug)
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
[dir='rtl']
|
||||||
|
&__nav-toggler-wrapper {
|
||||||
|
left: unset;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__nav-toggler-wrapper {
|
&__nav-toggler-wrapper {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -219,6 +219,10 @@ export const API: React.FC<EditViewProps> = (props) => {
|
|||||||
editConfig && 'API' in editConfig && 'actions' in editConfig.API ? editConfig.API.actions : []
|
editConfig && 'API' in editConfig && 'actions' in editConfig.API ? editConfig.API.actions : []
|
||||||
|
|
||||||
setViewActions(apiActions)
|
setViewActions(apiActions)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setViewActions([])
|
||||||
|
}
|
||||||
}, [collection, global, setViewActions])
|
}, [collection, global, setViewActions])
|
||||||
|
|
||||||
const localeOptions =
|
const localeOptions =
|
||||||
|
|||||||
@@ -25,23 +25,27 @@ const AccountView: React.FC = () => {
|
|||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
const userRef = useRef(user)
|
const userRef = useRef(user)
|
||||||
const [internalState, setInternalState] = useState<Fields>()
|
const [internalState, setInternalState] = useState<Fields>()
|
||||||
const { id, docPermissions, getDocPermissions, getDocPreferences, preferencesKey, slug } =
|
const {
|
||||||
useDocumentInfo()
|
id,
|
||||||
|
slug,
|
||||||
|
collection,
|
||||||
|
docPermissions,
|
||||||
|
getDocPermissions,
|
||||||
|
getDocPreferences,
|
||||||
|
preferencesKey,
|
||||||
|
} = useDocumentInfo()
|
||||||
const { getPreference } = usePreferences()
|
const { getPreference } = usePreferences()
|
||||||
|
|
||||||
const config = useConfig()
|
const config = useConfig()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
admin: { components: { views: { Account: CustomAccountComponent } = {} } = {} },
|
admin: { components: { views: { Account: CustomAccountComponent } = {} } = {} },
|
||||||
collections,
|
|
||||||
routes: { api },
|
routes: { api },
|
||||||
serverURL,
|
serverURL,
|
||||||
} = useConfig()
|
} = useConfig()
|
||||||
|
|
||||||
const { t } = useTranslation('authentication')
|
const { t } = useTranslation('authentication')
|
||||||
|
|
||||||
const collection = collections.find((coll) => coll.slug === slug)
|
|
||||||
|
|
||||||
const { fields } = collection || {}
|
const { fields } = collection || {}
|
||||||
|
|
||||||
const [{ data, isLoading: isLoadingData }] = usePayloadAPI(`${serverURL}${api}/${slug}/${id}`, {
|
const [{ data, isLoading: isLoadingData }] = usePayloadAPI(`${serverURL}${api}/${slug}/${id}`, {
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
|
|||||||
: []
|
: []
|
||||||
|
|
||||||
setViewActions(defaultActions)
|
setViewActions(defaultActions)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setViewActions([])
|
||||||
|
}
|
||||||
}, [global.slug, location.pathname, global?.admin?.components?.views?.Edit, setViewActions])
|
}, [global.slug, location.pathname, global?.admin?.components?.views?.Edit, setViewActions])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const GlobalView: React.FC<IndexProps> = (props) => {
|
|||||||
|
|
||||||
const { reportUpdate } = useDocumentEvents()
|
const { reportUpdate } = useDocumentEvents()
|
||||||
|
|
||||||
const { admin: { components: { views: { Edit: Edit } = {} } = {} } = {}, fields, slug } = global
|
const { slug, admin: { components: { views: { Edit: Edit } = {} } = {} } = {}, fields } = global
|
||||||
|
|
||||||
const onSave = useCallback(
|
const onSave = useCallback(
|
||||||
async (json) => {
|
async (json) => {
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ export const LivePreviewView: React.FC<
|
|||||||
: []
|
: []
|
||||||
|
|
||||||
setViewActions(livePreviewActions)
|
setViewActions(livePreviewActions)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setViewActions([])
|
||||||
|
}
|
||||||
}, [collection, global, setViewActions])
|
}, [collection, global, setViewActions])
|
||||||
|
|
||||||
const breakpoints: LivePreviewConfig['breakpoints'] = [
|
const breakpoints: LivePreviewConfig['breakpoints'] = [
|
||||||
|
|||||||
@@ -184,6 +184,10 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
|
|||||||
: []
|
: []
|
||||||
|
|
||||||
setViewActions(versionActions)
|
setViewActions(versionActions)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setViewActions([])
|
||||||
|
}
|
||||||
}, [collection, global, setViewActions])
|
}, [collection, global, setViewActions])
|
||||||
|
|
||||||
let metaTitle: string
|
let metaTitle: string
|
||||||
|
|||||||
@@ -136,6 +136,10 @@ const VersionsView: React.FC<IndexProps> = (props) => {
|
|||||||
: []
|
: []
|
||||||
|
|
||||||
setViewActions(versionsActions)
|
setViewActions(versionsActions)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setViewActions([])
|
||||||
|
}
|
||||||
}, [collection, global, setViewActions])
|
}, [collection, global, setViewActions])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => {
|
|||||||
: []
|
: []
|
||||||
|
|
||||||
setViewActions(defaultActions)
|
setViewActions(defaultActions)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setViewActions([])
|
||||||
|
}
|
||||||
}, [id, location.pathname, collection?.admin?.components?.views?.Edit, setViewActions])
|
}, [id, location.pathname, collection?.admin?.components?.views?.Edit, setViewActions])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { fieldAffectsData } from '../../../../../fields/config/types'
|
|||||||
|
|
||||||
const formatFields = (collection: SanitizedCollectionConfig, isEditing?: boolean): Field[] =>
|
const formatFields = (collection: SanitizedCollectionConfig, isEditing?: boolean): Field[] =>
|
||||||
isEditing
|
isEditing
|
||||||
? collection.fields.filter((field) => (fieldAffectsData(field) && field.name !== 'id') || true)
|
? collection.fields.filter((field) => !fieldAffectsData(field) || field.name !== 'id')
|
||||||
: collection.fields
|
: collection.fields
|
||||||
|
|
||||||
export default formatFields
|
export default formatFields
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import formatFields from './formatFields'
|
|||||||
const EditView: React.FC<IndexProps> = (props) => {
|
const EditView: React.FC<IndexProps> = (props) => {
|
||||||
const { collection: incomingCollection, isEditing } = props
|
const { collection: incomingCollection, isEditing } = props
|
||||||
|
|
||||||
const { admin: { components: { views: { Edit } = {} } = {} } = {}, slug: collectionSlug } =
|
const { slug: collectionSlug, admin: { components: { views: { Edit } = {} } = {} } = {} } =
|
||||||
incomingCollection
|
incomingCollection
|
||||||
|
|
||||||
const [fields] = useState(() => formatFields(incomingCollection, isEditing))
|
const [fields] = useState(() => formatFields(incomingCollection, isEditing))
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ const ListView: React.FC<ListIndexProps> = (props) => {
|
|||||||
if (CustomList && typeof CustomList === 'object' && 'actions' in CustomList) {
|
if (CustomList && typeof CustomList === 'object' && 'actions' in CustomList) {
|
||||||
setViewActions(CustomList.actions || [])
|
setViewActions(CustomList.actions || [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setViewActions([])
|
||||||
|
}
|
||||||
}, [CustomList, setViewActions])
|
}, [CustomList, setViewActions])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -40,8 +40,14 @@ export const formatUseAsTitle = (args: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const field = fieldFromProps || getObjectDotNotation<FormField>(doc, collection.admin.useAsTitle)
|
const field = fieldFromProps || getObjectDotNotation<FormField>(doc, collection.admin.useAsTitle)
|
||||||
|
let title: string
|
||||||
let title = typeof field === 'string' ? field : (field?.value as string)
|
if (typeof field === 'string') {
|
||||||
|
title = field
|
||||||
|
} else if (typeof field === 'number') {
|
||||||
|
title = String(field)
|
||||||
|
} else {
|
||||||
|
title = field?.value as string
|
||||||
|
}
|
||||||
|
|
||||||
const fieldConfig = collection?.fields?.find((f) => 'name' in f && f?.name === useAsTitle)
|
const fieldConfig = collection?.fields?.find((f) => 'name' in f && f?.name === useAsTitle)
|
||||||
const isDate = fieldConfig?.type === 'date'
|
const isDate = fieldConfig?.type === 'date'
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
import type { Payload } from '../../../payload'
|
import type { Payload } from '../../../payload'
|
||||||
|
|
||||||
import formatName from '../../../graphql/utilities/formatName'
|
import formatName from '../../../graphql/utilities/formatName'
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import access from '../../operations/access'
|
import access from '../../operations/access'
|
||||||
|
|
||||||
const formatConfigNames = (results, configs) => {
|
const formatConfigNames = (results, configs) => {
|
||||||
@@ -19,7 +20,7 @@ const formatConfigNames = (results, configs) => {
|
|||||||
function accessResolver(payload: Payload) {
|
function accessResolver(payload: Payload) {
|
||||||
async function resolver(_, args, context) {
|
async function resolver(_, args, context) {
|
||||||
const options = {
|
const options = {
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessResults = await access(options)
|
const accessResults = await access(options)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Collection } from '../../../collections/config/types'
|
import type { Collection } from '../../../collections/config/types'
|
||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import forgotPassword from '../../operations/forgotPassword'
|
import forgotPassword from '../../operations/forgotPassword'
|
||||||
|
|
||||||
function forgotPasswordResolver(collection: Collection): any {
|
function forgotPasswordResolver(collection: Collection): any {
|
||||||
@@ -12,7 +13,7 @@ function forgotPasswordResolver(collection: Collection): any {
|
|||||||
},
|
},
|
||||||
disableEmail: args.disableEmail,
|
disableEmail: args.disableEmail,
|
||||||
expiration: args.expiration,
|
expiration: args.expiration,
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
|
||||||
}
|
}
|
||||||
|
|
||||||
await forgotPassword(options)
|
await forgotPassword(options)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import init from '../../operations/init'
|
import init from '../../operations/init'
|
||||||
|
|
||||||
function initResolver(collection: string) {
|
function initResolver(collection: string) {
|
||||||
async function resolver(_, args, context) {
|
async function resolver(_, args, context) {
|
||||||
const options = {
|
const options = {
|
||||||
collection,
|
collection,
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return init(options)
|
return init(options)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Collection } from '../../../collections/config/types'
|
import type { Collection } from '../../../collections/config/types'
|
||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import login from '../../operations/login'
|
import login from '../../operations/login'
|
||||||
|
|
||||||
function loginResolver(collection: Collection) {
|
function loginResolver(collection: Collection) {
|
||||||
@@ -12,7 +13,7 @@ function loginResolver(collection: Collection) {
|
|||||||
password: args.password,
|
password: args.password,
|
||||||
},
|
},
|
||||||
depth: 0,
|
depth: 0,
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
|
||||||
res: context.res,
|
res: context.res,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import type { Collection } from '../../../collections/config/types'
|
import type { Collection } from '../../../collections/config/types'
|
||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import logout from '../../operations/logout'
|
import logout from '../../operations/logout'
|
||||||
|
|
||||||
function logoutResolver(collection: Collection): any {
|
function logoutResolver(collection: Collection): any {
|
||||||
async function resolver(_, args, context) {
|
async function resolver(_, args, context) {
|
||||||
const options = {
|
const options = {
|
||||||
collection,
|
collection,
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
|
||||||
res: context.res,
|
res: context.res,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Collection } from '../../../collections/config/types'
|
import type { Collection } from '../../../collections/config/types'
|
||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import me from '../../operations/me'
|
import me from '../../operations/me'
|
||||||
|
|
||||||
function meResolver(collection: Collection): any {
|
function meResolver(collection: Collection): any {
|
||||||
@@ -8,7 +9,7 @@ function meResolver(collection: Collection): any {
|
|||||||
const options = {
|
const options = {
|
||||||
collection,
|
collection,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
|
||||||
}
|
}
|
||||||
return me(options)
|
return me(options)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Collection } from '../../../collections/config/types'
|
import type { Collection } from '../../../collections/config/types'
|
||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import getExtractJWT from '../../getExtractJWT'
|
import getExtractJWT from '../../getExtractJWT'
|
||||||
import refresh from '../../operations/refresh'
|
import refresh from '../../operations/refresh'
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ function refreshResolver(collection: Collection) {
|
|||||||
const options = {
|
const options = {
|
||||||
collection,
|
collection,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
|
||||||
res: context.res,
|
res: context.res,
|
||||||
token,
|
token,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import type { Collection } from '../../../collections/config/types'
|
import type { Collection } from '../../../collections/config/types'
|
||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import resetPassword from '../../operations/resetPassword'
|
import resetPassword from '../../operations/resetPassword'
|
||||||
|
|
||||||
function resetPasswordResolver(collection: Collection) {
|
function resetPasswordResolver(collection: Collection) {
|
||||||
async function resolver(_, args, context) {
|
async function resolver(_, args, context) {
|
||||||
if (args.locale) context.req.locale = args.locale
|
let { req } = context
|
||||||
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale
|
req = isolateObjectProperty(req, 'locale')
|
||||||
|
req = isolateObjectProperty(req, 'fallbackLocale')
|
||||||
|
if (args.locale) req.locale = args.locale
|
||||||
|
if (args.fallbackLocale) req.fallbackLocale = args.fallbackLocale
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
api: 'GraphQL',
|
api: 'GraphQL',
|
||||||
collection,
|
collection,
|
||||||
data: args,
|
data: args,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(req, 'transactionID'),
|
||||||
res: context.res,
|
res: context.res,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Collection } from '../../../collections/config/types'
|
import type { Collection } from '../../../collections/config/types'
|
||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import unlock from '../../operations/unlock'
|
import unlock from '../../operations/unlock'
|
||||||
|
|
||||||
function unlockResolver(collection: Collection) {
|
function unlockResolver(collection: Collection) {
|
||||||
@@ -8,7 +9,7 @@ function unlockResolver(collection: Collection) {
|
|||||||
const options = {
|
const options = {
|
||||||
collection,
|
collection,
|
||||||
data: { email: args.email },
|
data: { email: args.email },
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await unlock(options)
|
const result = await unlock(options)
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import type { Collection } from '../../../collections/config/types'
|
import type { Collection } from '../../../collections/config/types'
|
||||||
|
import type { PayloadRequest } from '../../../express/types'
|
||||||
|
|
||||||
import isolateTransactionID from '../../../utilities/isolateTransactionID'
|
import isolateObjectProperty from '../../../utilities/isolateObjectProperty'
|
||||||
import verifyEmail from '../../operations/verifyEmail'
|
import verifyEmail from '../../operations/verifyEmail'
|
||||||
|
|
||||||
function verifyEmailResolver(collection: Collection) {
|
function verifyEmailResolver(collection: Collection) {
|
||||||
async function resolver(_, args, context) {
|
async function resolver(_, args, context) {
|
||||||
if (args.locale) context.req.locale = args.locale
|
let { req } = context
|
||||||
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale
|
req = isolateObjectProperty(req, 'locale')
|
||||||
|
req = isolateObjectProperty(req, 'fallbackLocale')
|
||||||
|
if (args.locale) req.locale = args.locale
|
||||||
|
if (args.fallbackLocale) req.fallbackLocale = args.fallbackLocale
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
api: 'GraphQL',
|
api: 'GraphQL',
|
||||||
collection,
|
collection,
|
||||||
req: isolateTransactionID(context.req),
|
req: isolateObjectProperty<PayloadRequest>(req, 'transactionID'),
|
||||||
res: context.res,
|
res: context.res,
|
||||||
token: args.token,
|
token: args.token,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
context,
|
context,
|
||||||
data,
|
data,
|
||||||
depth,
|
depth,
|
||||||
fallbackLocale,
|
fallbackLocale: fallbackLocaleArg = options?.req?.fallbackLocale,
|
||||||
locale,
|
locale: localeArg = null,
|
||||||
overrideAccess = true,
|
overrideAccess = true,
|
||||||
req = {} as PayloadRequest,
|
req = {} as PayloadRequest,
|
||||||
res,
|
res,
|
||||||
@@ -46,6 +46,12 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
setRequestContext(req, context)
|
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(
|
||||||
@@ -56,8 +62,6 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
req.payloadAPI = req.payloadAPI || 'local'
|
req.payloadAPI = req.payloadAPI || 'local'
|
||||||
req.payload = payload
|
req.payload = payload
|
||||||
req.i18n = i18nInit(payload.config.i18n)
|
req.i18n = i18nInit(payload.config.i18n)
|
||||||
req.locale = undefined
|
|
||||||
req.fallbackLocale = undefined
|
|
||||||
|
|
||||||
if (!req.t) req.t = req.i18n.t
|
if (!req.t) req.t = req.i18n.t
|
||||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||||
@@ -73,7 +77,10 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (locale) args.req.locale = locale
|
if (locale) args.req.locale = locale
|
||||||
if (fallbackLocale) args.req.fallbackLocale = fallbackLocale
|
if (fallbackLocale) {
|
||||||
|
args.req.fallbackLocale =
|
||||||
|
typeof fallbackLocaleArg !== 'undefined' ? fallbackLocaleArg : fallbackLocale || defaultLocale
|
||||||
|
}
|
||||||
|
|
||||||
return login<TSlug>(args)
|
return login<TSlug>(args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const incrementLoginAttempts = async ({
|
|||||||
} = collection
|
} = collection
|
||||||
|
|
||||||
if ('lockUntil' in doc && typeof doc.lockUntil === 'string') {
|
if ('lockUntil' in doc && typeof doc.lockUntil === 'string') {
|
||||||
const lockUntil = Math.floor(new Date(doc.lockUntil).getTime() / 1000)
|
const lockUntil = new Date(doc.lockUntil).getTime()
|
||||||
|
|
||||||
// Expired lock, restart count at 1
|
// Expired lock, restart count at 1
|
||||||
if (lockUntil < Date.now()) {
|
if (lockUntil < Date.now()) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user