Compare commits
52 Commits
db-postgre
...
db-postgre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
769bee82cd | ||
|
|
55eb6e7583 | ||
|
|
a907480a94 | ||
|
|
9c25754eed | ||
|
|
7c9ec9c4e0 | ||
|
|
a0bd7060c4 | ||
|
|
169da5c3d8 | ||
|
|
25932c0db6 | ||
|
|
3bce3d4240 | ||
|
|
e2a13deff6 | ||
|
|
5d99adfd38 | ||
|
|
819753a637 | ||
|
|
b7d65ab717 | ||
|
|
e365300bee | ||
|
|
4d9f494a80 | ||
|
|
fa6d4596dc | ||
|
|
08b02ecba2 | ||
|
|
eba777c3a0 | ||
|
|
bfe6918681 | ||
|
|
e5d6cdae38 | ||
|
|
218f2ead03 | ||
|
|
e9c1222182 | ||
|
|
c8ed6454a7 | ||
|
|
4077598777 | ||
|
|
65d7d54ba3 | ||
|
|
6690c37c4e | ||
|
|
0efc610210 | ||
|
|
cc99c3a619 | ||
|
|
24a8dc7aa3 | ||
|
|
90764efa9a | ||
|
|
d05e3b0411 | ||
|
|
e4bc281fc2 | ||
|
|
9d05b82dc6 | ||
|
|
f2284f3d1b | ||
|
|
1347b6cc36 | ||
|
|
0a56d50334 | ||
|
|
02999a5659 | ||
|
|
365127bee4 | ||
|
|
b67e97aa7f | ||
|
|
61e8ce1743 | ||
|
|
034aa68cd4 | ||
|
|
268e6c485e | ||
|
|
4c1a5dca44 | ||
|
|
a12d1f4755 | ||
|
|
d55be73992 | ||
|
|
b9f236ae50 | ||
|
|
1d38e6d5d5 | ||
|
|
2f3c994cea | ||
|
|
0586f236bb | ||
|
|
d582619ead | ||
|
|
17fc2d13d0 | ||
|
|
800ffd2611 |
75
.github/ISSUE_TEMPLATE/1.bug_report_v3.yml
vendored
75
.github/ISSUE_TEMPLATE/1.bug_report_v3.yml
vendored
@@ -1,14 +1,58 @@
|
||||
name: Bug Report v3
|
||||
description: Create a bug report for Payload v3 (beta)
|
||||
labels: ['status: needs-triage', 'v3']
|
||||
name: Functionality Bug
|
||||
description: '[REPRODUCTION REQUIRED] - Create a bug report'
|
||||
labels: ['status: needs-triage', 'v3', 'validate-reproduction']
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the Bug
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: reproduction-link
|
||||
attributes:
|
||||
label: Link to reproduction
|
||||
description: Want us to look into your issue faster? Follow the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md) for more information.
|
||||
label: Link to the code that reproduces this issue
|
||||
description: >-
|
||||
_REQUIRED_: Please provide a link to your reproduction. Note, if the URL is invalid (404 or a private repository), we may close the issue.
|
||||
Either use `npx create-payload-app@beta -t blank` then push to a repo or follow the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md) for more information.
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
description: Steps to reproduce the behavior, please provide a clear description of how to reproduce the issue, based on the linked minimal reproduction. Screenshots can be provided in the issue body below. If using code blocks, make sure that [syntax highlighting is correct](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) and double check that the rendered preview is not broken.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Which area(s) are affected? (Select all that apply)
|
||||
multiple: true
|
||||
options:
|
||||
- 'Not sure'
|
||||
- 'area: core'
|
||||
- 'area: docs'
|
||||
- 'area: templates'
|
||||
- 'area: ui'
|
||||
- 'db-mongodb'
|
||||
- 'db-postgres'
|
||||
- 'db-sqlite'
|
||||
- 'db-vercel-postgres'
|
||||
- 'plugin: cloud'
|
||||
- 'plugin: cloud-storage'
|
||||
- 'plugin: form-builder'
|
||||
- 'plugin: nested-docs'
|
||||
- 'plugin: richtext-lexical'
|
||||
- 'plugin: richtext-slate'
|
||||
- 'plugin: search'
|
||||
- 'plugin: sentry'
|
||||
- 'plugin: seo'
|
||||
- 'plugin: stripe'
|
||||
- 'plugin: other'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
@@ -22,25 +66,6 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the Bug
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
description: Steps to reproduce the behavior, please provide a clear description of how to reproduce the issue, based on the linked minimal reproduction. Screenshots can be provided in the issue body below. If using code blocks, make sure that [syntax highlighting is correct](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) and double check that the rendered preview is not broken.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: adapters-plugins
|
||||
attributes:
|
||||
label: Adapters and Plugins
|
||||
description: What adapters and plugins are you using if relevant? ie. db-mongodb, db-postgres, storage-vercel-blob, etc.
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Before submitting the issue, go through the steps you've written down to make sure the steps provided are detailed and clear.
|
||||
|
||||
41
.github/ISSUE_TEMPLATE/2.design_issue.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/2.design_issue.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Design Issue
|
||||
description: '[SCREENSHOT REQUIRED] - Create a design issue report'
|
||||
labels: ['status: needs-triage', 'v3', 'area: ui']
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the Bug.
|
||||
description: >-
|
||||
_REQUIRED:_ Please a screenshot/video of the issue along with a detailed description of the problem.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
description: Steps to reproduce the behavior, please provide a clear description of how to reproduce the issue, based on the linked minimal reproduction. Screenshots can be provided in the issue body below. If using code blocks, make sure that [syntax highlighting is correct](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) and double check that the rendered preview is not broken.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment Info
|
||||
description: Paste output from `pnpm payload info` (>= beta.92) _or_ Payload, Node.js, and Next.js versions.
|
||||
render: text
|
||||
placeholder: |
|
||||
Payload:
|
||||
Node.js:
|
||||
Next.js:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Before submitting the issue, go through the steps you've written down to make sure the steps provided are detailed and clear.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Contributors should be able to follow the steps provided in order to reproduce the bug.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: These steps are used to add integration tests to ensure the same issue does not happen again. Thanks in advance!
|
||||
@@ -1,11 +1,11 @@
|
||||
name: Bug Report
|
||||
description: Create a bug report for Payload
|
||||
name: v2 Bug Report
|
||||
description: Report a bug for Payload v2. ONLY CRITICAL bugs will be fixed in v2.
|
||||
labels: ['status: needs-triage', 'v2']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
*Note:* Feature requests should be opened as [discussions](https://github.com/payloadcms/payload/discussions/new?category=feature-requests-ideas).
|
||||
ONLY CRITICAL bugs will be fixed in v2.
|
||||
- type: input
|
||||
id: reproduction-link
|
||||
attributes:
|
||||
23
.github/PULL_REQUEST_TEMPLATE.md
vendored
23
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,10 +1,23 @@
|
||||
<!--
|
||||
|
||||
For external contributors, please include:
|
||||
Thank you for the PR! Please go through the checklist below and make sure you've completed all the steps.
|
||||
|
||||
- A summary of the pull request and any related issues it fixes.
|
||||
- Reasoning for the changes made or any additional context that may be useful.
|
||||
Please review the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository if you haven't already.
|
||||
|
||||
Ensure you have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository.
|
||||
The following items will ensure that your PR is handled as smoothly as possible:
|
||||
|
||||
-->
|
||||
- PR Title must follow conventional commits format. For example, `feat: my new feature`, `fix(plugin-seo): my fix`.
|
||||
- Minimal description explained as if explained to someone not immediately familiar with the code.
|
||||
- Provide before/after screenshots or code diffs if applicable.
|
||||
- Link any related issues/discussions from GitHub or Discord.
|
||||
- Add review comments if necessary to explain to the reviewer the logic behind a change
|
||||
|
||||
### What?
|
||||
|
||||
### Why?
|
||||
|
||||
### How?
|
||||
|
||||
Fixes #
|
||||
|
||||
-->
|
||||
|
||||
13
.github/actions/triage/.eslintrc.js
vendored
Normal file
13
.github/actions/triage/.eslintrc.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
}
|
||||
8
.github/actions/triage/.prettierrc.js
vendored
Normal file
8
.github/actions/triage/.prettierrc.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
parser: 'typescript',
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
arrowParens: 'avoid',
|
||||
}
|
||||
22
.github/actions/triage/LICENSE
vendored
Normal file
22
.github/actions/triage/LICENSE
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Payload <info@payloadcms.com>. All modification and additions are copyright of Payload.
|
||||
|
||||
---
|
||||
|
||||
Original license:
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2023, Balázs Orbán
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
21
.github/actions/triage/README.md
vendored
Normal file
21
.github/actions/triage/README.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Triage
|
||||
|
||||
Modified version of https://github.com/balazsorban44/nissuer
|
||||
|
||||
## Modifications
|
||||
|
||||
- Port to TypeScript
|
||||
- Remove issue locking
|
||||
- Remove reproduction blocklist
|
||||
- Uses `@vercel/ncc` for packaging
|
||||
|
||||
## Development
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Whenever a modification is made to the action, the action built to `dist` must be committed to the repository.
|
||||
|
||||
This is done by running:
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
||||
40
.github/actions/triage/action.yml
vendored
Normal file
40
.github/actions/triage/action.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Triage
|
||||
description: Initial triage for issues
|
||||
|
||||
inputs:
|
||||
reproduction-comment:
|
||||
description: 'Either a string or a path to a .md file inside the repository. Example: ".github/invalid-reproduction.md"'
|
||||
default: '.github/invalid-reproduction.md'
|
||||
reproduction-hosts:
|
||||
description: 'Comma-separated list of hostnames that are allowed for reproductions. Example: "github.com,codesandbox.io"'
|
||||
default: github.com
|
||||
reproduction-invalid-label:
|
||||
description: 'Label to apply to issues without a valid reproduction. Example: "invalid-reproduction"'
|
||||
default: 'invalid-reproduction'
|
||||
reproduction-issue-labels:
|
||||
description: 'Comma-separated list of issue labels. If configured, only verify reproduction URLs of issues with one of these labels present. Adding a comma at the end will handle non-labeled issues as invalid. Example: "bug,", will consider issues with the label "bug" or no label.'
|
||||
default: ''
|
||||
reproduction-link-section:
|
||||
description: 'A regular expression string with "(.*)" matching a valid URL in the issue body. The result is trimmed. Example: "### Link to reproduction(.*)### To reproduce"'
|
||||
default: '### Link to reproduction(.*)### To reproduce'
|
||||
tag-only:
|
||||
description: Log and tag only. Do not perform closing or commenting actions.
|
||||
default: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/checkout@v4
|
||||
- name: Run action
|
||||
run: node ${{ github.action_path }}/dist/index.js
|
||||
shell: sh
|
||||
# https://github.com/actions/runner/issues/665#issuecomment-676581170
|
||||
env:
|
||||
"INPUT_REPRODUCTION_COMMENT": ${{inputs.reproduction-comment}}
|
||||
"INPUT_REPRODUCTION_HOSTS": ${{inputs.reproduction-hosts}}
|
||||
"INPUT_REPRODUCTION_INVALID_LABEL": ${{inputs.reproduction-invalid-label}}
|
||||
"INPUT_REPRODUCTION_ISSUE_LABELS": ${{inputs.reproduction-issue-labels}}
|
||||
"INPUT_REPRODUCTION_LINK_SECTION": ${{inputs.reproduction-link-section}}
|
||||
"INPUT_TAG_ONLY": ${{inputs.tag-only}}
|
||||
34048
.github/actions/triage/dist/index.js
vendored
Normal file
34048
.github/actions/triage/dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
.github/actions/triage/jest.config.js
vendored
Normal file
7
.github/actions/triage/jest.config.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/dist/'],
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': ['@swc/jest'],
|
||||
},
|
||||
}
|
||||
34
.github/actions/triage/package.json
vendored
Normal file
34
.github/actions/triage/package.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "triage",
|
||||
"version": "0.0.0",
|
||||
"description": "GitHub Action to triage new issues",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"build": "pnpm build:typecheck && pnpm build:ncc",
|
||||
"build:ncc": "ncc build src/index.ts -t -o dist",
|
||||
"build:typecheck": "tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.3.0",
|
||||
"@actions/github": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/webhooks-types": "^7.5.1",
|
||||
"@swc/jest": "^0.2.36",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^20.16.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^26.5.6",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
5419
.github/actions/triage/pnpm-lock.yaml
generated
vendored
Normal file
5419
.github/actions/triage/pnpm-lock.yaml
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
195
.github/actions/triage/src/index.ts
vendored
Normal file
195
.github/actions/triage/src/index.ts
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
import { debug, error, getBooleanInput, getInput, info, setFailed } from '@actions/core'
|
||||
|
||||
import { context, getOctokit } from '@actions/github'
|
||||
import { readFile, access } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
// Ensure GITHUB_TOKEN and GITHUB_WORKSPACE are present
|
||||
if (!process.env.GITHUB_TOKEN) throw new TypeError('No GITHUB_TOKEN provided')
|
||||
if (!process.env.GITHUB_WORKSPACE) throw new TypeError('Not a GitHub workspace')
|
||||
|
||||
// Define the configuration object
|
||||
interface Config {
|
||||
invalidLink: {
|
||||
comment: string
|
||||
bugLabels: string[]
|
||||
hosts: string[]
|
||||
label: string
|
||||
linkSection: string
|
||||
}
|
||||
tagOnly: boolean
|
||||
token: string
|
||||
workspace: string
|
||||
}
|
||||
|
||||
const config: Config = {
|
||||
invalidLink: {
|
||||
comment: getInput('reproduction_comment') || '.github/invalid-reproduction.md',
|
||||
bugLabels: getInput('reproduction_issue_labels')
|
||||
.split(',')
|
||||
.map(l => l.trim()),
|
||||
hosts: (getInput('reproduction_hosts') || 'github.com').split(',').map(h => h.trim()),
|
||||
label: getInput('reproduction_invalid_label') || 'invalid-reproduction',
|
||||
linkSection:
|
||||
getInput('reproduction_link_section') || '### Link to reproduction(.*)### To reproduce',
|
||||
},
|
||||
tagOnly: getBooleanOrUndefined('tag_only') || false,
|
||||
token: process.env.GITHUB_TOKEN,
|
||||
workspace: process.env.GITHUB_WORKSPACE,
|
||||
}
|
||||
|
||||
// Attempt to parse JSON, return parsed object or error
|
||||
function tryParse(json: string): Record<string, unknown> {
|
||||
try {
|
||||
return JSON.parse(json)
|
||||
} catch (e) {
|
||||
setFailed(`Could not parse JSON: ${e instanceof Error ? e.message : e}`)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves a boolean input or undefined based on environment variables
|
||||
function getBooleanOrUndefined(value: string): boolean | undefined {
|
||||
const variable = process.env[`INPUT_${value.toUpperCase()}`]
|
||||
return variable === undefined || variable === '' ? undefined : getBooleanInput(value)
|
||||
}
|
||||
|
||||
// Returns the appropriate label match type
|
||||
function getLabelMatch(value: string | undefined): 'name' | 'description' {
|
||||
return value === 'name' ? 'name' : 'description'
|
||||
}
|
||||
|
||||
// Function to check if an issue contains a valid reproduction link
|
||||
async function checkValidReproduction(): Promise<void> {
|
||||
const { issue, action } = context.payload as {
|
||||
issue: { number: number; body: string; labels: { name: string }[] } | undefined
|
||||
action: string
|
||||
}
|
||||
|
||||
if (action !== 'opened' || !issue?.body) return
|
||||
|
||||
const labels = issue.labels.map(l => l.name)
|
||||
|
||||
const issueMatchingLabel =
|
||||
labels.length &&
|
||||
config.invalidLink.bugLabels.length &&
|
||||
labels.some(l => config.invalidLink.bugLabels.includes(l))
|
||||
|
||||
if (!issueMatchingLabel) {
|
||||
info(
|
||||
`Issue #${issue.number} does not match required labels: ${config.invalidLink.bugLabels.join(', ')}`,
|
||||
)
|
||||
info(`Issue labels: ${labels.join(', ')}`)
|
||||
return
|
||||
}
|
||||
|
||||
info(`Issue #${issue.number} labels: ${labels.join(', ')}`)
|
||||
|
||||
const { rest: client } = getOctokit(config.token)
|
||||
const common = { ...context.repo, issue_number: issue.number }
|
||||
|
||||
const labelsToRemove = labels.filter(l => config.invalidLink.bugLabels.includes(l))
|
||||
|
||||
if (await isValidReproduction(issue.body)) {
|
||||
await Promise.all(
|
||||
labelsToRemove.map(label => client.issues.removeLabel({ ...common, name: label })),
|
||||
)
|
||||
|
||||
return info(`Issue #${issue.number} contains a valid reproduction 💚`)
|
||||
}
|
||||
|
||||
info(`Invalid reproduction, issue will be closed/labeled/commented...`)
|
||||
|
||||
// Adjust labels
|
||||
await Promise.all(
|
||||
labelsToRemove.map(label => client.issues.removeLabel({ ...common, name: label })),
|
||||
)
|
||||
info(`Issue #${issue.number} - validate label removed`)
|
||||
await client.issues.addLabels({ ...common, labels: [config.invalidLink.label] })
|
||||
info(`Issue #${issue.number} - labeled`)
|
||||
|
||||
// If tagOnly, do not close or comment
|
||||
if (config.tagOnly) {
|
||||
info('Tag-only enabled, no closing/commenting actions taken')
|
||||
return
|
||||
}
|
||||
|
||||
// Perform closing and commenting actions
|
||||
await client.issues.update({ ...common, state: 'closed' })
|
||||
info(`Issue #${issue.number} - closed`)
|
||||
|
||||
const comment = join(config.workspace, config.invalidLink.comment)
|
||||
await client.issues.createComment({ ...common, body: await getCommentBody(comment) })
|
||||
info(`Issue #${issue.number} - commented`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an issue contains a valid/accessible link to a reproduction.
|
||||
*
|
||||
* Returns `true` if the link is valid.
|
||||
* @param body - The body content of the issue
|
||||
*/
|
||||
async function isValidReproduction(body: string): Promise<boolean> {
|
||||
const linkSectionRe = new RegExp(config.invalidLink.linkSection, 'is')
|
||||
const link = body.match(linkSectionRe)?.[1]?.trim()
|
||||
|
||||
if (!link) {
|
||||
info('Missing link')
|
||||
info(`Link section regex: ${linkSectionRe}`)
|
||||
info(`Link section: ${body}`)
|
||||
return false
|
||||
}
|
||||
|
||||
info(`Checking validity of link: ${link}`)
|
||||
|
||||
if (!URL.canParse(link)) {
|
||||
info(`Invalid URL: ${link}`)
|
||||
return false
|
||||
}
|
||||
|
||||
const url = new URL(link)
|
||||
|
||||
if (!config.invalidLink.hosts.includes(url.hostname)) {
|
||||
info('Link did not match allowed reproduction hosts')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify that the link can be accessed
|
||||
const response = await fetch(link)
|
||||
const isOk = response.status < 400 || response.status >= 500
|
||||
|
||||
info(`Link status: ${response.status}`)
|
||||
if (!isOk) {
|
||||
info(`Link returned status ${response.status}`)
|
||||
}
|
||||
return isOk
|
||||
} catch (error) {
|
||||
info(`Error fetching link: ${(error as Error).message}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return either a file's content or a string
|
||||
* @param {string} pathOrComment
|
||||
*/
|
||||
async function getCommentBody(pathOrComment: string) {
|
||||
try {
|
||||
await access(pathOrComment)
|
||||
return await readFile(pathOrComment, 'utf8')
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ENOENT') return pathOrComment
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const { token, workspace, ...safeConfig } = config
|
||||
info('Configuration:')
|
||||
info(JSON.stringify(safeConfig, null, 2))
|
||||
|
||||
await checkValidReproduction()
|
||||
}
|
||||
|
||||
run().catch(setFailed)
|
||||
15
.github/actions/triage/tsconfig.json
vendored
Normal file
15
.github/actions/triage/tsconfig.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es2020.string"],
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": false, // Undo this
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
||||
18
.github/comments/invalid-reproduction.md
vendored
Normal file
18
.github/comments/invalid-reproduction.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
We cannot recreate the issue with the provided information. **Please add a reproduction in order for us to be able to investigate.**
|
||||
|
||||
### Why was this issue marked with the `invalid-reproduction` label?
|
||||
|
||||
To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository created with `create-payload-app@beta -t blank` or a forked/branched version of this repository with tests added (more info in the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md)).
|
||||
|
||||
To make sure the issue is resolved as quickly as possible, please make sure that the reproduction is as **minimal** as possible. This means that you should **remove unnecessary code, files, and dependencies** that do not contribute to the issue. Ensure your reproduction does not depend on secrets, 3rd party registries, private dependencies, or any other data that cannot be made public. Avoid a reproduction including a whole monorepo (unless relevant to the issue). The easier it is to reproduce the issue, the quicker we can help.
|
||||
|
||||
Please test your reproduction against the latest version of Payload to make sure your issue has not already been fixed.
|
||||
|
||||
### I added a link, why was it still marked?
|
||||
|
||||
Ensure the link is pointing to a codebase that is accessible (e.g. not a private repository). "[example.com](http://example.com/)", "n/a", "will add later", etc. are not acceptable links -- we need to see a public codebase. See the above section for accepted links.
|
||||
|
||||
### Useful Resources
|
||||
|
||||
- [Reproduction Guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md)
|
||||
- [Contributing to Payload](https://www.youtube.com/watch?v=08Qa3ggR9rw)
|
||||
31
.github/dependabot.yml
vendored
31
.github/dependabot.yml
vendored
@@ -31,17 +31,44 @@ updates:
|
||||
labels:
|
||||
- dependencies
|
||||
groups:
|
||||
production:
|
||||
production-deps:
|
||||
dependency-type: production
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
patterns:
|
||||
- '*'
|
||||
dev:
|
||||
exclude-patterns:
|
||||
- 'drizzle*'
|
||||
dev-deps:
|
||||
dependency-type: development
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
patterns:
|
||||
- '*'
|
||||
exclude-patterns:
|
||||
- 'drizzle*'
|
||||
|
||||
# Only bump patch versions for 2.x
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
target-branch: main
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: sunday
|
||||
timezone: America/Detroit
|
||||
time: '06:00'
|
||||
commit-message:
|
||||
prefix: 'chore(deps)'
|
||||
labels:
|
||||
- dependencies
|
||||
groups:
|
||||
production-deps:
|
||||
dependency-type: production
|
||||
update-types:
|
||||
- patch
|
||||
patterns:
|
||||
- '*'
|
||||
exclude-patterns:
|
||||
- 'drizzle*'
|
||||
|
||||
49
.github/pnpm-lock.yaml
generated
vendored
49
.github/pnpm-lock.yaml
generated
vendored
@@ -55,6 +55,55 @@ importers:
|
||||
specifier: ^4.9.5
|
||||
version: 4.9.5
|
||||
|
||||
actions/triage:
|
||||
dependencies:
|
||||
'@actions/core':
|
||||
specifier: ^1.3.0
|
||||
version: 1.10.1
|
||||
'@actions/github':
|
||||
specifier: ^5.0.0
|
||||
version: 5.1.1
|
||||
devDependencies:
|
||||
'@octokit/webhooks-types':
|
||||
specifier: ^7.5.1
|
||||
version: 7.5.1
|
||||
'@swc/jest':
|
||||
specifier: ^0.2.36
|
||||
version: 0.2.36(@swc/core@1.7.23)
|
||||
'@types/jest':
|
||||
specifier: ^27.5.2
|
||||
version: 27.5.2
|
||||
'@types/node':
|
||||
specifier: ^20.16.5
|
||||
version: 20.16.5
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^4.33.0
|
||||
version: 4.33.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^4.33.0
|
||||
version: 4.33.0(eslint@7.32.0)(typescript@4.9.5)
|
||||
'@vercel/ncc':
|
||||
specifier: 0.38.1
|
||||
version: 0.38.1
|
||||
concurrently:
|
||||
specifier: ^8.2.2
|
||||
version: 8.2.2
|
||||
eslint:
|
||||
specifier: ^7.32.0
|
||||
version: 7.32.0
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@20.16.5)(node-notifier@8.0.2)
|
||||
prettier:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
ts-jest:
|
||||
specifier: ^26.5.6
|
||||
version: 26.5.6(jest@29.7.0(@types/node@20.16.5)(node-notifier@8.0.2))(typescript@4.9.5)
|
||||
typescript:
|
||||
specifier: ^4.9.5
|
||||
version: 4.9.5
|
||||
|
||||
packages:
|
||||
|
||||
'@actions/core@1.10.1':
|
||||
|
||||
1
.github/workflows/lock-issues.yml
vendored
1
.github/workflows/lock-issues.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
with:
|
||||
process-only: 'issues'
|
||||
issue-inactive-days: '1'
|
||||
exclude-any-issue-labels: 'status: awaiting-reply'
|
||||
log-output: true
|
||||
issue-comment: >
|
||||
This issue has been automatically locked.
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
name: label-author
|
||||
name: triage
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
types:
|
||||
- opened
|
||||
issues:
|
||||
types: [opened]
|
||||
types:
|
||||
- opened
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
debug-context:
|
||||
@@ -18,10 +23,10 @@ jobs:
|
||||
- name: View context attributes
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: console.log(context)
|
||||
script: console.log({ context })
|
||||
|
||||
label-created-by:
|
||||
name: Label pr/issue on opening
|
||||
name: label-on-open
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Tag with 'created-by'
|
||||
@@ -42,6 +47,8 @@ jobs:
|
||||
'PatrikKozak',
|
||||
'tylandavis',
|
||||
'paulpopus',
|
||||
'r1tsuu',
|
||||
'GermanJablo',
|
||||
];
|
||||
|
||||
const type = context.payload.pull_request ? 'pull_request' : 'issue';
|
||||
@@ -78,3 +85,18 @@ jobs:
|
||||
labels: [label],
|
||||
});
|
||||
console.log(`Added '${label}' label.`);
|
||||
|
||||
triage:
|
||||
name: initial-triage
|
||||
if: github.event_name == 'issues'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: ./.github/actions/triage
|
||||
with:
|
||||
reproduction-comment: '.github/comments/invalid-reproduction.md'
|
||||
reproduction-link-section: '### Link to the code that reproduces this issue(.*)### Reproduction Steps'
|
||||
reproduction-issue-labels: 'validate-reproduction'
|
||||
tag-only: 'true'
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,3 +1,50 @@
|
||||
## [2.30.4](https://github.com/payloadcms/payload/compare/v2.30.3...v2.30.4) (2024-11-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **plugin-seo:** add Turkish translation ([#8918](https://github.com/payloadcms/payload/issues/8918)) ([7c9ec9c](https://github.com/payloadcms/payload/commit/7c9ec9c4e0bafd19eb0ef94e9bb4aa07df3e097e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **db-postgres:** sort by localized fields ([#9016](https://github.com/payloadcms/payload/issues/9016)) ([9c25754](https://github.com/payloadcms/payload/commit/9c25754eed87e70b053231693102378150b8b80d))
|
||||
* edit many modal draft action button order and style ([#9046](https://github.com/payloadcms/payload/issues/9046)) ([a907480](https://github.com/payloadcms/payload/commit/a907480a94f07b4afa0e0723a3738c8bd457c51f)), closes [#9045](https://github.com/payloadcms/payload/issues/9045)
|
||||
* error saving after duplicating blocks with nested items ([#8814](https://github.com/payloadcms/payload/issues/8814)) ([eba777c](https://github.com/payloadcms/payload/commit/eba777c3a0b4c489d87d3c6154d835a105fef4a2))
|
||||
* list drawer relationship not displaying ([#9011](https://github.com/payloadcms/payload/issues/9011)) ([169da5c](https://github.com/payloadcms/payload/commit/169da5c3d83bfe3559227504929aa62721ded1d9))
|
||||
* querying relationships by `id` path with REST ([#9014](https://github.com/payloadcms/payload/issues/9014)) ([a0bd706](https://github.com/payloadcms/payload/commit/a0bd7060c45df3037ee074bd83400837c0adb83a))
|
||||
|
||||
## [2.30.3](https://github.com/payloadcms/payload/compare/v2.30.2...v2.30.3) (2024-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **db-postgres:** migrate:create errors with previous schemas ([#8786](https://github.com/payloadcms/payload/issues/8786)) ([e9c1222](https://github.com/payloadcms/payload/commit/e9c12221824a9a180991722135d22ff91d07ef11))
|
||||
* duplicate with select hasMany fields ([#8734](https://github.com/payloadcms/payload/issues/8734)) ([c8ed645](https://github.com/payloadcms/payload/commit/c8ed6454a733bea09ae620517c4894701999e119))
|
||||
|
||||
## [2.30.2](https://github.com/payloadcms/payload/compare/v2.30.1...v2.30.2) (2024-10-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* applies resize after cropping if `resizeOptions` are defined ([#8535](https://github.com/payloadcms/payload/issues/8535)) ([f2284f3](https://github.com/payloadcms/payload/commit/f2284f3d1b420c543d9eee9929f961db4cbef8a1))
|
||||
* calculates correct aspect ratio dimensions on sharp based files ([#8510](https://github.com/payloadcms/payload/issues/8510)) ([9d05b82](https://github.com/payloadcms/payload/commit/9d05b82dc67b967e55176b92f7b20d4486883201)), closes [#8317](https://github.com/payloadcms/payload/issues/8317)
|
||||
* **db-postgres:** build indexes for relationships ([#8446](https://github.com/payloadcms/payload/issues/8446)) ([d05e3b0](https://github.com/payloadcms/payload/commit/d05e3b0411c2e705bd7a26e93ba55f2b7532e41b))
|
||||
* **db-postgres:** port many various fixes from 3.0 ([#8468](https://github.com/payloadcms/payload/issues/8468)) ([1347b6c](https://github.com/payloadcms/payload/commit/1347b6cc36c33043755b4e31d7731ba19b8c985f))
|
||||
* **db-postgres:** select hasMany nested to array + tab/group ([#8739](https://github.com/payloadcms/payload/issues/8739)) ([0efc610](https://github.com/payloadcms/payload/commit/0efc6102104729a162912157a7e34293ec9764a5))
|
||||
* **richtext-lexical:** add target _blank for new-tab in linkFeature ([#8571](https://github.com/payloadcms/payload/issues/8571)) ([61e8ce1](https://github.com/payloadcms/payload/commit/61e8ce17439301cb16aee92887e97a69cac4e044)), closes [#8569](https://github.com/payloadcms/payload/issues/8569)
|
||||
|
||||
## [2.30.1](https://github.com/payloadcms/payload/compare/v2.30.0...v2.30.1) (2024-10-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **db-mongodb:** properly filters out `number` field values with the `exists` operator filter ([#8415](https://github.com/payloadcms/payload/issues/8415)) ([0586f23](https://github.com/payloadcms/payload/commit/0586f236bbf04163a0d9b226772849cb3d977864)), closes [#8181](https://github.com/payloadcms/payload/issues/8181)
|
||||
* sorting by id incorrectly orders by version.id ([#8450](https://github.com/payloadcms/payload/issues/8450)) ([1d38e6d](https://github.com/payloadcms/payload/commit/1d38e6d5d5b56a91aa8f59a461d40f28b1750f8c))
|
||||
|
||||
## [2.30.0](https://github.com/payloadcms/payload/compare/v2.29.0...v2.30.0) (2024-09-27)
|
||||
|
||||
* export toast from react toastify in payload ([#8438](https://github.com/payloadcms/payload/issues/8438)) ([17fc2d1](https://github.com/payloadcms/payload/commit/17fc2d13d06b6de01f839c27fd706bc0d6a185eb))
|
||||
|
||||
## [2.29.0](https://github.com/payloadcms/payload/compare/v2.28.0...v2.29.0) (2024-09-25)
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +99,10 @@ If you want to add contributions to this repository, please follow the instructi
|
||||
|
||||
The [Examples Directory](./examples) is a great resource for learning how to setup Payload in a variety of different ways, but you can also find great examples in our blog and throughout our social media.
|
||||
|
||||
If you'd like to run the examples, you can either copy them to a folder outside this repo or run them directly by (1) navigating to the example's subfolder (`cd examples/your-example-folder`) and (2) using the `--ignore-workspace` flag to bypass workspace restrictions (e.g., `pnpm --ignore-workspace install` or `pnpm --ignore-workspace dev`).
|
||||
|
||||
You can see more examples at:
|
||||
|
||||
- [Examples Directory](./examples)
|
||||
- [Payload Blog](https://payloadcms.com/blog)
|
||||
- [Payload YouTube](https://www.youtube.com/@payloadcms)
|
||||
|
||||
@@ -24,8 +24,8 @@ export default buildConfig({
|
||||
// collections go here
|
||||
],
|
||||
localization: {
|
||||
locales: ['en', 'es', 'de'],
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'es', 'de'], // required
|
||||
defaultLocale: 'en', // required
|
||||
fallback: true,
|
||||
},
|
||||
})
|
||||
@@ -54,7 +54,7 @@ export default buildConfig({
|
||||
rtl: true,
|
||||
},
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
defaultLocale: 'en', // required
|
||||
fallback: true,
|
||||
},
|
||||
})
|
||||
@@ -87,7 +87,7 @@ export default buildConfig({
|
||||
code: 'nb',
|
||||
},
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
defaultLocale: 'en', // required
|
||||
fallback: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -66,8 +66,8 @@ export default buildConfig({
|
||||
admin: {
|
||||
bundler: webpackBundler(), // or viteBundler()
|
||||
},
|
||||
db: mongooseAdapter({}) // or postgresAdapter({}),
|
||||
editor: lexicalEditor({}) // or slateEditor({})
|
||||
db: mongooseAdapter({}), // or postgresAdapter({})
|
||||
editor: lexicalEditor({}), // or slateEditor({})
|
||||
collections: [
|
||||
{
|
||||
slug: 'pages',
|
||||
|
||||
@@ -144,6 +144,10 @@ export default addLastModified
|
||||
|
||||
### Available Plugins
|
||||
|
||||
You can discover existing plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin).
|
||||
Payload supports both official plugins, maintained by the Payload team, and community plugins, developed by external contributors.
|
||||
|
||||
You can discover existing plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin). These plugins offer a wide range of functionality. Some are maintained by the Payload team, while others are community-built. While we encourage users to explore them, please note that only official plugins are maintained and supported by the Payload team. For community plugins, support may vary as they are developed and maintained independently.
|
||||
|
||||
For maintainers building plugins for others to use, please add the topic to help others find it. If you would like one to be built by the core Payload team, [open a Feature Request](https://github.com/payloadcms/payload/discussions) in our GitHub Discussions board. We would be happy to review your code and maybe feature you and your plugin where appropriate.
|
||||
|
||||
For a list of official plugins, check the [Payload monorepo](https://github.com/payloadcms/payload/tree/main/packages).
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"copyfiles": "2.4.1",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "8.6.0",
|
||||
"drizzle-orm": "0.29.3",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"express": "4.18.2",
|
||||
"form-data": "3.0.1",
|
||||
"fs-extra": "10.1.0",
|
||||
|
||||
@@ -33,15 +33,15 @@
|
||||
"md5": "2.3.0",
|
||||
"mini-css-extract-plugin": "1.6.2",
|
||||
"path-browserify": "1.0.1",
|
||||
"postcss": "8.4.31",
|
||||
"postcss": "8.4.47",
|
||||
"postcss-loader": "6.2.1",
|
||||
"postcss-preset-env": "9.0.0",
|
||||
"process": "0.11.10",
|
||||
"sass-loader": "12.6.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"swc-loader": "^0.2.3",
|
||||
"swc-minify-webpack-plugin": "^2.1.0",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"swc-loader": "^0.2.6",
|
||||
"swc-minify-webpack-plugin": "^2.1.3",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"url-loader": "4.1.1",
|
||||
"webpack": "^5.78.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"bin": {
|
||||
|
||||
@@ -16,38 +16,38 @@ export function getValidTemplates(): ProjectTemplate[] {
|
||||
return [
|
||||
{
|
||||
name: 'blank',
|
||||
description: 'Blank Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
description: 'Blank Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank#v2.30.3',
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
description: 'Website Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/website',
|
||||
description: 'Website Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/website#v2.30.3',
|
||||
},
|
||||
{
|
||||
name: 'ecommerce',
|
||||
description: 'E-commerce Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/ecommerce',
|
||||
description: 'E-commerce Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/ecommerce#v2.30.3',
|
||||
},
|
||||
{
|
||||
name: 'plugin',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
type: 'plugin',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template',
|
||||
},
|
||||
{
|
||||
name: 'payload-demo',
|
||||
description: 'Payload demo site at https://demo.payloadcms.com',
|
||||
type: 'starter',
|
||||
description: 'Payload demo site at https://demo.payloadcms.com',
|
||||
url: 'https://github.com/payloadcms/public-demo',
|
||||
},
|
||||
{
|
||||
name: 'payload-website',
|
||||
description: 'Payload website CMS at https://payloadcms.com',
|
||||
type: 'starter',
|
||||
description: 'Payload website CMS at https://payloadcms.com',
|
||||
url: 'https://github.com/payloadcms/website-cms',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.3",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -55,6 +55,30 @@ const handleNonHasManyValues = (formattedValue, operator, path) => {
|
||||
}
|
||||
}
|
||||
|
||||
const buildExistsQuery = (formattedValue, path) => {
|
||||
if (formattedValue) {
|
||||
return {
|
||||
rawQuery: {
|
||||
$and: [
|
||||
{ [path]: { $exists: true } },
|
||||
{ [path]: { $ne: null } },
|
||||
{ [path]: { $ne: '' } }, // Exclude null and empty string
|
||||
],
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
rawQuery: {
|
||||
$or: [
|
||||
{ [path]: { $exists: false } },
|
||||
{ [path]: { $eq: null } },
|
||||
{ [path]: { $eq: '' } }, // Treat empty string as null / undefined
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const sanitizeQueryValue = ({
|
||||
field,
|
||||
hasCustomID,
|
||||
@@ -102,8 +126,16 @@ export const sanitizeQueryValue = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === 'number' && typeof formattedValue === 'string') {
|
||||
formattedValue = Number(val)
|
||||
if (field.type === 'number') {
|
||||
if (typeof formattedValue === 'string' && operator !== 'exists') {
|
||||
formattedValue = Number(val)
|
||||
}
|
||||
|
||||
if (operator === 'exists') {
|
||||
formattedValue = val === 'true' ? true : val === 'false' ? false : Boolean(val)
|
||||
|
||||
return buildExistsQuery(formattedValue, path)
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === 'date' && typeof val === 'string' && operator !== 'exists') {
|
||||
@@ -193,27 +225,7 @@ export const sanitizeQueryValue = ({
|
||||
if (operator === 'exists') {
|
||||
formattedValue = formattedValue === 'true' || formattedValue === true
|
||||
|
||||
if (formattedValue) {
|
||||
return {
|
||||
rawQuery: {
|
||||
$and: [
|
||||
{ [path]: { $exists: true } },
|
||||
{ [path]: { $ne: null } },
|
||||
{ [path]: { $ne: '' } },
|
||||
],
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
rawQuery: {
|
||||
$or: [
|
||||
{ [path]: { $exists: false } },
|
||||
{ [path]: { $eq: null } },
|
||||
{ [path]: { $eq: '' } }, // Treat empty string as null / undefined
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
return buildExistsQuery(formattedValue, path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.8.7",
|
||||
"version": "0.8.10",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -26,12 +26,12 @@
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.3.1",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.20.14-1f2c838",
|
||||
"drizzle-orm": "0.29.3",
|
||||
"drizzle-kit": "0.23.2-df9e596",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
"uuid": "9.0.0"
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { Payload } from 'payload'
|
||||
import type { Connect } from 'payload/database'
|
||||
|
||||
import { eq, sql } from 'drizzle-orm'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import { numeric, timestamp, varchar } from 'drizzle-orm/pg-core'
|
||||
import { Pool } from 'pg'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { PostgresAdapter } from './types'
|
||||
|
||||
import { pushDevSchema } from './utilities/pushDevSchema'
|
||||
|
||||
const connectWithReconnect = async function ({
|
||||
adapter,
|
||||
payload,
|
||||
@@ -48,6 +48,7 @@ const connectWithReconnect = async function ({
|
||||
|
||||
export const connect: Connect = async function connect(this: PostgresAdapter, payload) {
|
||||
this.schema = {
|
||||
pgSchema: this.pgSchema,
|
||||
...this.tables,
|
||||
...this.relations,
|
||||
...this.enums,
|
||||
@@ -77,76 +78,10 @@ export const connect: Connect = async function connect(this: PostgresAdapter, pa
|
||||
|
||||
// Only push schema if not in production
|
||||
if (
|
||||
process.env.NODE_ENV === 'production' ||
|
||||
process.env.PAYLOAD_MIGRATING === 'true' ||
|
||||
this.push === false
|
||||
)
|
||||
return
|
||||
|
||||
const { pushSchema } = require('drizzle-kit/payload')
|
||||
|
||||
// This will prompt if clarifications are needed for Drizzle to push new schema
|
||||
const { apply, hasDataLoss, statementsToExecute, warnings } = await pushSchema(
|
||||
this.schema,
|
||||
this.drizzle,
|
||||
)
|
||||
|
||||
if (warnings.length) {
|
||||
let message = `Warnings detected during schema push: \n\n${warnings.join('\n')}\n\n`
|
||||
|
||||
if (hasDataLoss) {
|
||||
message += `DATA LOSS WARNING: Possible data loss detected if schema is pushed.\n\n`
|
||||
}
|
||||
|
||||
message += `Accept warnings and push schema to database?`
|
||||
|
||||
const { confirm: acceptWarnings } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// Exit if user does not accept warnings.
|
||||
// Q: Is this the right type of exit for this interaction?
|
||||
if (!acceptWarnings) {
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
await apply()
|
||||
|
||||
// Migration table def in order to use query using drizzle
|
||||
const migrationsSchema = this.pgSchema.table('payload_migrations', {
|
||||
name: varchar('name'),
|
||||
batch: numeric('batch'),
|
||||
created_at: timestamp('created_at'),
|
||||
updated_at: timestamp('updated_at'),
|
||||
})
|
||||
|
||||
const devPush = await this.drizzle
|
||||
.select()
|
||||
.from(migrationsSchema)
|
||||
.where(eq(migrationsSchema.batch, '-1'))
|
||||
|
||||
if (!devPush.length) {
|
||||
await this.drizzle.insert(migrationsSchema).values({
|
||||
name: 'dev',
|
||||
batch: '-1',
|
||||
})
|
||||
} else {
|
||||
await this.drizzle
|
||||
.update(migrationsSchema)
|
||||
.set({
|
||||
updated_at: new Date(),
|
||||
})
|
||||
.where(eq(migrationsSchema.batch, '-1'))
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_MIGRATING !== 'true' &&
|
||||
this.push !== false
|
||||
) {
|
||||
await pushDevSchema(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Count } from 'payload/database'
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { count as sqlCount } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { ChainedMethods } from './find/chainMethods'
|
||||
@@ -51,8 +51,7 @@ export const count: Count = async function count(
|
||||
methods: selectCountMethods,
|
||||
query: db
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(DISTINCT ${this.tables[tableName].id})`,
|
||||
count: sqlCount(),
|
||||
})
|
||||
.from(table)
|
||||
.where(where),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
|
||||
import type { CreateMigration } from 'payload/database'
|
||||
|
||||
import fs from 'fs'
|
||||
@@ -43,12 +43,13 @@ const getDefaultDrizzleSnapshot = (): DrizzleSnapshotJSON => ({
|
||||
schemas: {},
|
||||
tables: {},
|
||||
},
|
||||
dialect: 'pg',
|
||||
dialect: 'postgresql',
|
||||
enums: {},
|
||||
prevId: '00000000-0000-0000-0000-00000000000',
|
||||
schemas: {},
|
||||
sequences: {},
|
||||
tables: {},
|
||||
version: '5',
|
||||
version: '7',
|
||||
})
|
||||
|
||||
export const createMigration: CreateMigration = async function createMigration(
|
||||
@@ -60,7 +61,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
|
||||
const { generateDrizzleJson, generateMigration, upPgSnapshot } = require('drizzle-kit/api')
|
||||
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
@@ -76,6 +77,12 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
|
||||
let drizzleJsonBefore = getDefaultDrizzleSnapshot()
|
||||
|
||||
if (this.schemaName) {
|
||||
drizzleJsonBefore.schemas = {
|
||||
[this.schemaName]: this.schemaName,
|
||||
}
|
||||
}
|
||||
|
||||
// Get latest migration snapshot
|
||||
const latestSnapshot = fs
|
||||
.readdirSync(dir)
|
||||
@@ -92,6 +99,11 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
}
|
||||
|
||||
const drizzleJsonAfter = generateDrizzleJson(this.schema)
|
||||
|
||||
if (drizzleJsonBefore.version < drizzleJsonAfter.version) {
|
||||
drizzleJsonBefore = upPgSnapshot(drizzleJsonBefore)
|
||||
}
|
||||
|
||||
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
|
||||
const sqlStatementsDown = await generateMigration(drizzleJsonAfter, drizzleJsonBefore)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { FindArgs } from 'payload/database'
|
||||
import type { Field, PayloadRequest, TypeWithID } from 'payload/types'
|
||||
|
||||
import { inArray, sql } from 'drizzle-orm'
|
||||
import { inArray, count as sqlCount } from 'drizzle-orm'
|
||||
|
||||
import type { PostgresAdapter } from '../types'
|
||||
import type { ChainedMethods } from './chainMethods'
|
||||
@@ -143,8 +143,7 @@ export const findMany = async function find({
|
||||
methods: selectCountMethods,
|
||||
query: db
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(DISTINCT ${adapter.tables[tableName].id})`,
|
||||
count: sqlCount(),
|
||||
})
|
||||
.from(table)
|
||||
.where(where),
|
||||
|
||||
@@ -14,11 +14,11 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
if (this.schemaName) {
|
||||
this.pgSchema = pgSchema(this.schemaName)
|
||||
} else {
|
||||
this.pgSchema = { table: pgTable }
|
||||
this.pgSchema = { enum: pgEnum, table: pgTable }
|
||||
}
|
||||
|
||||
if (this.payload.config.localization) {
|
||||
this.enums.enum__locales = pgEnum(
|
||||
this.enums.enum__locales = this.pgSchema.enum(
|
||||
'_locales',
|
||||
this.payload.config.localization.locales.map(({ code }) => code) as [string, ...string[]],
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
|
||||
let latestBatch = 0
|
||||
let migrationsInDB = []
|
||||
|
||||
const hasMigrationTable = await migrationTableExists(this.drizzle)
|
||||
const hasMigrationTable = await migrationTableExists(this)
|
||||
|
||||
if (hasMigrationTable) {
|
||||
;({ docs: migrationsInDB } = await payload.find({
|
||||
@@ -80,7 +80,7 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
|
||||
}
|
||||
|
||||
async function runMigrationFile(payload: Payload, migration: Migration, batch: number) {
|
||||
const { generateDrizzleJson } = require('drizzle-kit/payload')
|
||||
const { generateDrizzleJson } = require('drizzle-kit/api')
|
||||
|
||||
const start = Date.now()
|
||||
const req = { payload } as PayloadRequest
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function migrateDown(this: PostgresAdapter): Promise<void> {
|
||||
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
|
||||
})
|
||||
|
||||
const tableExists = await migrationTableExists(this.drizzle)
|
||||
const tableExists = await migrationTableExists(this)
|
||||
if (tableExists) {
|
||||
await payload.delete({
|
||||
id: migration.id,
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function migrateRefresh(this: PostgresAdapter) {
|
||||
msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`,
|
||||
})
|
||||
|
||||
const tableExists = await migrationTableExists(this.drizzle)
|
||||
const tableExists = await migrationTableExists(this)
|
||||
if (tableExists) {
|
||||
await payload.delete({
|
||||
collection: 'payload-migrations',
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function migrateReset(this: PostgresAdapter): Promise<void> {
|
||||
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
|
||||
})
|
||||
|
||||
const tableExists = await migrationTableExists(this.drizzle)
|
||||
const tableExists = await migrationTableExists(this)
|
||||
if (tableExists) {
|
||||
await payload.delete({
|
||||
id: migration.id,
|
||||
@@ -68,7 +68,7 @@ export async function migrateReset(this: PostgresAdapter): Promise<void> {
|
||||
|
||||
// Delete dev migration
|
||||
|
||||
const tableExists = await migrationTableExists(this.drizzle)
|
||||
const tableExists = await migrationTableExists(this)
|
||||
if (tableExists) {
|
||||
try {
|
||||
await payload.delete({
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function migrateStatus(this: PostgresAdapter): Promise<void> {
|
||||
})
|
||||
|
||||
let existingMigrations = []
|
||||
const hasMigrationTable = await migrationTableExists(this.drizzle)
|
||||
const hasMigrationTable = await migrationTableExists(this)
|
||||
|
||||
if (hasMigrationTable) {
|
||||
;({ existingMigrations } = await getMigrations({ payload }))
|
||||
|
||||
@@ -185,17 +185,12 @@ export const getTableColumnFromPath = ({
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
|
||||
joins[tableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
adapter.tables[newTableName]._parentID,
|
||||
)
|
||||
let condition = eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID)
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
condition = and(condition, eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
joins[tableName] = condition
|
||||
}
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
@@ -224,17 +219,15 @@ export const getTableColumnFromPath = ({
|
||||
)
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
const conditions = [
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
)
|
||||
]
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
joins[newTableName] = and(...conditions)
|
||||
} else {
|
||||
joins[newTableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
@@ -268,17 +261,12 @@ export const getTableColumnFromPath = ({
|
||||
]
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
...joinConstraints,
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
)
|
||||
const conditions = [...joinConstraints]
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: 'locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
joins[newTableName] = and(...conditions)
|
||||
} else {
|
||||
joins[newTableName] = and(...joinConstraints)
|
||||
}
|
||||
@@ -302,17 +290,12 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
eq(arrayParentTable.id, adapter.tables[newTableName]._parentID),
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
)
|
||||
const conditions = [eq(arrayParentTable.id, adapter.tables[newTableName]._parentID)]
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
joins[newTableName] = and(...conditions)
|
||||
} else {
|
||||
joins[newTableName] = eq(arrayParentTable.id, adapter.tables[newTableName]._parentID)
|
||||
}
|
||||
@@ -399,17 +382,18 @@ export const getTableColumnFromPath = ({
|
||||
constraints = constraints.concat(blockConstraints)
|
||||
selectFields = { ...selectFields, ...blockSelectFields }
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
)
|
||||
if (locale) {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
const conditions = [
|
||||
eq(
|
||||
(aliasTable || adapter.tables[tableName]).id,
|
||||
adapter.tables[newTableName]._parentID,
|
||||
),
|
||||
]
|
||||
|
||||
if (locale !== 'all') {
|
||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
joins[newTableName] = and(...conditions)
|
||||
} else {
|
||||
joins[newTableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
@@ -444,21 +428,20 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
// Join in the relationships table
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
const conditions = [
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
eq(aliasRelationshipTable.locale, locale),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
]
|
||||
|
||||
if (locale !== 'all') {
|
||||
conditions.push(eq(aliasRelationshipTable.locale, locale))
|
||||
}
|
||||
|
||||
joinAliases.push({
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
eq(aliasRelationshipTable.locale, locale),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
condition: and(...conditions),
|
||||
table: aliasRelationshipTable,
|
||||
})
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: 'locale',
|
||||
table: aliasRelationshipTable,
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Join in the relationships table
|
||||
joinAliases.push({
|
||||
@@ -554,17 +537,14 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
const parentTable = aliasTable || adapter.tables[tableName]
|
||||
|
||||
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
|
||||
|
||||
aliasTable = undefined
|
||||
let condition = eq(parentTable.id, adapter.tables[newTableName]._parentID)
|
||||
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
condition = and(condition, eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
joins[newTableName] = condition
|
||||
|
||||
aliasTable = undefined
|
||||
}
|
||||
|
||||
const targetTable = aliasTable || adapter.tables[newTableName]
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { SQL } from 'drizzle-orm'
|
||||
import type { Field, Operator, Where } from 'payload/types'
|
||||
|
||||
import { and, ilike, isNotNull, isNull, ne, notInArray, or, sql } from 'drizzle-orm'
|
||||
import { PgUUID } from 'drizzle-orm/pg-core'
|
||||
import { QueryError } from 'payload/errors'
|
||||
import { validOperators } from 'payload/types'
|
||||
|
||||
@@ -174,6 +175,7 @@ export async function parseParams({
|
||||
const sanitizedQueryValue = sanitizeQueryValue({
|
||||
adapter,
|
||||
field,
|
||||
isUUID: table?.[columnName] instanceof PgUUID,
|
||||
operator,
|
||||
relationOrPath,
|
||||
val,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { APIError } from 'payload/errors'
|
||||
import { type Field, type TabAsField, fieldAffectsData } from 'payload/types'
|
||||
import { createArrayFromCommaDelineated } from 'payload/utilities'
|
||||
import { validate as uuidValidate } from 'uuid'
|
||||
|
||||
import type { PostgresAdapter } from '../types'
|
||||
|
||||
type SanitizeQueryValueArgs = {
|
||||
adapter: PostgresAdapter
|
||||
field: Field | TabAsField
|
||||
isUUID: boolean
|
||||
operator: string
|
||||
relationOrPath: string
|
||||
val: any
|
||||
@@ -15,6 +17,7 @@ type SanitizeQueryValueArgs = {
|
||||
export const sanitizeQueryValue = ({
|
||||
adapter,
|
||||
field,
|
||||
isUUID,
|
||||
operator: operatorArg,
|
||||
relationOrPath,
|
||||
val,
|
||||
@@ -64,6 +67,16 @@ export const sanitizeQueryValue = ({
|
||||
|
||||
if (field.type === 'number' && typeof formattedValue === 'string') {
|
||||
formattedValue = Number(val)
|
||||
|
||||
if (Number.isNaN(formattedValue)) {
|
||||
formattedValue = null
|
||||
}
|
||||
}
|
||||
|
||||
if (isUUID && typeof formattedValue === 'string') {
|
||||
if (!uuidValidate(val)) {
|
||||
formattedValue = null
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === 'date' && operator !== 'exists') {
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
PgTableWithColumns,
|
||||
UniqueConstraintBuilder,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import { Field, fieldAffectsData } from 'payload/types'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import { relations } from 'drizzle-orm'
|
||||
import {
|
||||
@@ -20,10 +20,12 @@ import {
|
||||
unique,
|
||||
varchar,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import { fieldAffectsData } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types'
|
||||
|
||||
import { createIndex } from './createIndex'
|
||||
import { createTableName } from './createTableName'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap'
|
||||
import { setColumnID } from './setColumnID'
|
||||
@@ -51,9 +53,17 @@ type Args = {
|
||||
tableName: string
|
||||
timestamps?: boolean
|
||||
versions: boolean
|
||||
/**
|
||||
* Tracks whether or not this table is built
|
||||
* from the result of a localized array or block field at some point
|
||||
*/
|
||||
withinLocalizedArrayOrBlock?: boolean
|
||||
}
|
||||
|
||||
type Result = {
|
||||
hasLocalizedManyNumberField: boolean
|
||||
hasLocalizedManyTextField: boolean
|
||||
hasLocalizedRelationshipField: boolean
|
||||
hasManyNumberField: 'index' | boolean
|
||||
hasManyTextField: 'index' | boolean
|
||||
relationsToBuild: Map<string, string>
|
||||
@@ -76,6 +86,7 @@ export const buildTable = ({
|
||||
tableName,
|
||||
timestamps,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
}: Args): Result => {
|
||||
const rootTableName = incomingRootTableName || tableName
|
||||
const columns: Record<string, PgColumnBuilder> = baseColumns
|
||||
@@ -124,6 +135,7 @@ export const buildTable = ({
|
||||
rootTableIDColType: rootTableIDColType || idColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (timestamps) {
|
||||
@@ -328,16 +340,28 @@ export const buildTable = ({
|
||||
if (relatedCollectionCustomIDType === 'number') colType = 'numeric'
|
||||
if (relatedCollectionCustomIDType === 'text') colType = 'varchar'
|
||||
|
||||
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
|
||||
`${formattedRelationTo}_id`,
|
||||
)
|
||||
const colName = `${relationTo}ID`
|
||||
|
||||
relationshipColumns[colName] = parentIDColumnMap[colType](`${formattedRelationTo}_id`)
|
||||
|
||||
relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
|
||||
foreignKey({
|
||||
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
|
||||
columns: [cols[`${relationTo}ID`]],
|
||||
columns: [cols[colName]],
|
||||
foreignColumns: [adapter.tables[formattedRelationTo].id],
|
||||
}).onDelete('cascade')
|
||||
|
||||
const indexName = [colName]
|
||||
|
||||
if (hasLocalizedRelationshipField) {
|
||||
indexName.push('locale')
|
||||
}
|
||||
|
||||
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
|
||||
name: indexName,
|
||||
columnName: `${formattedRelationTo}_id`,
|
||||
tableName: relationshipsTableName,
|
||||
})
|
||||
})
|
||||
|
||||
relationshipsTable = adapter.pgSchema.table(
|
||||
@@ -431,5 +455,12 @@ export const buildTable = ({
|
||||
|
||||
adapter.relations[`relations_${tableName}`] = tableRelations
|
||||
|
||||
return { hasManyNumberField, hasManyTextField, relationsToBuild }
|
||||
return {
|
||||
hasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField,
|
||||
hasManyNumberField,
|
||||
hasManyTextField,
|
||||
relationsToBuild,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
pgEnum,
|
||||
text,
|
||||
timestamp,
|
||||
varchar,
|
||||
@@ -57,6 +56,11 @@ type Args = {
|
||||
rootTableIDColType: string
|
||||
rootTableName: string
|
||||
versions: boolean
|
||||
/**
|
||||
* Tracks whether or not this table is built
|
||||
* from the result of a localized array or block field at some point
|
||||
*/
|
||||
withinLocalizedArrayOrBlock?: boolean
|
||||
}
|
||||
|
||||
type Result = {
|
||||
@@ -91,6 +95,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
}: Args): Result => {
|
||||
const throwValidationError = true
|
||||
let hasLocalizedField = false
|
||||
@@ -152,7 +157,11 @@ export const traverseFields = ({
|
||||
switch (field.type) {
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
|
||||
@@ -181,7 +190,12 @@ export const traverseFields = ({
|
||||
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
|
||||
@@ -232,7 +246,7 @@ export const traverseFields = ({
|
||||
throwValidationError,
|
||||
})
|
||||
|
||||
adapter.enums[enumName] = pgEnum(
|
||||
adapter.enums[enumName] = adapter.pgSchema.enum(
|
||||
enumName,
|
||||
field.options.map((option) => {
|
||||
if (optionIsObject(option)) {
|
||||
@@ -268,7 +282,12 @@ export const traverseFields = ({
|
||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns.locale = adapter.enums.enum__locales('locale').notNull()
|
||||
baseExtraConfig.localeIdx = (cols) =>
|
||||
index(`${selectTableName}_locale_idx`).on(cols.locale)
|
||||
@@ -342,13 +361,21 @@ export const traverseFields = ({
|
||||
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
|
||||
}
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||
baseExtraConfig._localeIdx = (cols) =>
|
||||
index(`${arrayTableName}_locale_idx`).on(cols._locale)
|
||||
}
|
||||
|
||||
const {
|
||||
hasLocalizedManyNumberField: subHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: subHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: subHasLocalizedRelationshipField,
|
||||
hasManyNumberField: subHasManyNumberField,
|
||||
hasManyTextField: subHasManyTextField,
|
||||
relationsToBuild: subRelationsToBuild,
|
||||
@@ -365,8 +392,21 @@ export const traverseFields = ({
|
||||
rootTableName,
|
||||
tableName: arrayTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock: isLocalized,
|
||||
})
|
||||
|
||||
if (subHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = subHasLocalizedManyNumberField
|
||||
}
|
||||
|
||||
if (subHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = subHasLocalizedRelationshipField
|
||||
}
|
||||
|
||||
if (subHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = subHasLocalizedManyTextField
|
||||
}
|
||||
|
||||
if (subHasManyTextField) {
|
||||
if (!hasManyTextField || subHasManyTextField === 'index')
|
||||
hasManyTextField = subHasManyTextField
|
||||
@@ -433,13 +473,21 @@ export const traverseFields = ({
|
||||
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
|
||||
}
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||
baseExtraConfig._localeIdx = (cols) =>
|
||||
index(`${blockTableName}_locale_idx`).on(cols._locale)
|
||||
}
|
||||
|
||||
const {
|
||||
hasLocalizedManyNumberField: subHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: subHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: subHasLocalizedRelationshipField,
|
||||
hasManyNumberField: subHasManyNumberField,
|
||||
hasManyTextField: subHasManyTextField,
|
||||
relationsToBuild: subRelationsToBuild,
|
||||
@@ -456,8 +504,21 @@ export const traverseFields = ({
|
||||
rootTableName,
|
||||
tableName: blockTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock: isLocalized,
|
||||
})
|
||||
|
||||
if (subHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = subHasLocalizedManyNumberField
|
||||
}
|
||||
|
||||
if (subHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = subHasLocalizedRelationshipField
|
||||
}
|
||||
|
||||
if (subHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = subHasLocalizedManyTextField
|
||||
}
|
||||
|
||||
if (subHasManyTextField) {
|
||||
if (!hasManyTextField || subHasManyTextField === 'index')
|
||||
hasManyTextField = subHasManyTextField
|
||||
@@ -541,6 +602,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) hasLocalizedField = true
|
||||
@@ -584,6 +646,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) hasLocalizedField = true
|
||||
@@ -628,6 +691,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (tabHasLocalizedField) hasLocalizedField = true
|
||||
@@ -672,6 +736,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (rowHasLocalizedField) hasLocalizedField = true
|
||||
@@ -691,7 +756,10 @@ export const traverseFields = ({
|
||||
relationships.add(field.relationTo)
|
||||
}
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
if (
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
break
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { NumberField } from 'payload/types'
|
||||
|
||||
type Args = {
|
||||
@@ -6,10 +5,29 @@ type Args = {
|
||||
locale?: string
|
||||
numberRows: Record<string, unknown>[]
|
||||
ref: Record<string, unknown>
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const transformHasManyNumber = ({ field, locale, numberRows, ref }: Args) => {
|
||||
const result = numberRows.map(({ number }) => number)
|
||||
export const transformHasManyNumber = ({
|
||||
field,
|
||||
locale,
|
||||
numberRows,
|
||||
ref,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
let result: unknown[]
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
result = numberRows.reduce((acc, { locale, number }) => {
|
||||
if (locale === withinArrayOrBlockLocale) {
|
||||
acc.push(number)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
} else {
|
||||
result = numberRows.map(({ number }) => number)
|
||||
}
|
||||
|
||||
if (locale) {
|
||||
ref[field.name][locale] = result
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { TextField } from 'payload/types'
|
||||
|
||||
type Args = {
|
||||
@@ -6,10 +5,29 @@ type Args = {
|
||||
locale?: string
|
||||
ref: Record<string, unknown>
|
||||
textRows: Record<string, unknown>[]
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const transformHasManyText = ({ field, locale, ref, textRows }: Args) => {
|
||||
const result = textRows.map(({ text }) => text)
|
||||
export const transformHasManyText = ({
|
||||
field,
|
||||
locale,
|
||||
ref,
|
||||
textRows,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
let result: unknown[]
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
result = textRows.reduce((acc, { locale, text }) => {
|
||||
if (locale === withinArrayOrBlockLocale) {
|
||||
acc.push(text)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
} else {
|
||||
result = textRows.map(({ text }) => text)
|
||||
}
|
||||
|
||||
if (locale) {
|
||||
ref[field.name][locale] = result
|
||||
|
||||
@@ -6,21 +6,31 @@ type Args = {
|
||||
locale?: string
|
||||
ref: Record<string, unknown>
|
||||
relations: Record<string, unknown>[]
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const transformRelationship = ({ field, locale, ref, relations }: Args) => {
|
||||
export const transformRelationship = ({
|
||||
field,
|
||||
locale,
|
||||
ref,
|
||||
relations,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
let result: unknown
|
||||
|
||||
if (!('hasMany' in field) || field.hasMany === false) {
|
||||
const relation = relations[0]
|
||||
let relation = relations[0]
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
relation = relations.find((rel) => rel.locale === withinArrayOrBlockLocale)
|
||||
}
|
||||
|
||||
if (relation) {
|
||||
// Handle hasOne Poly
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
const matchedRelation = Object.entries(relation).find(
|
||||
([key, val]) =>
|
||||
val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key),
|
||||
)
|
||||
const matchedRelation = Object.entries(relation).find(([key, val]) => {
|
||||
return val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key)
|
||||
})
|
||||
|
||||
if (matchedRelation) {
|
||||
const relationTo = matchedRelation[0].replace('ID', '')
|
||||
@@ -40,18 +50,26 @@ export const transformRelationship = ({ field, locale, ref, relations }: Args) =
|
||||
const transformedRelations = []
|
||||
|
||||
relations.forEach((relation) => {
|
||||
let matchedLocale = true
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
matchedLocale = relation.locale === withinArrayOrBlockLocale
|
||||
}
|
||||
|
||||
// Handle hasMany
|
||||
if (!Array.isArray(field.relationTo)) {
|
||||
const relatedData = relation[`${field.relationTo}ID`]
|
||||
|
||||
if (relatedData) {
|
||||
if (relatedData && matchedLocale) {
|
||||
transformedRelations.push(relatedData)
|
||||
}
|
||||
} else {
|
||||
// Handle hasMany Poly
|
||||
const matchedRelation = Object.entries(relation).find(
|
||||
([key, val]) =>
|
||||
val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key),
|
||||
val !== null &&
|
||||
!['id', 'locale', 'order', 'parent', 'path'].includes(key) &&
|
||||
matchedLocale,
|
||||
)
|
||||
|
||||
if (matchedRelation) {
|
||||
|
||||
@@ -55,6 +55,10 @@ type TraverseFieldsArgs = {
|
||||
* All hasMany text fields, as returned by Drizzle, keyed on an object by field path
|
||||
*/
|
||||
texts: Record<string, Record<string, unknown>[]>
|
||||
/**
|
||||
* Set to a locale if this group of fields is within a localized array or block.
|
||||
*/
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
// Traverse fields recursively, transforming data
|
||||
@@ -71,6 +75,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}: TraverseFieldsArgs): T => {
|
||||
const sanitizedPath = path ? `${path}.` : path
|
||||
|
||||
@@ -88,6 +93,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,6 +114,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -145,6 +152,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: locale,
|
||||
})
|
||||
|
||||
if ('_order' in rowResult) {
|
||||
@@ -157,7 +165,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
return arrayResult
|
||||
}, {})
|
||||
} else {
|
||||
result[field.name] = fieldData.map((row, i) => {
|
||||
result[field.name] = fieldData.reduce((acc, row, i) => {
|
||||
if (row._uuid) {
|
||||
row.id = row._uuid
|
||||
delete row._uuid
|
||||
@@ -167,34 +175,48 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
delete row._order
|
||||
}
|
||||
|
||||
return traverseFields<T>({
|
||||
blocks,
|
||||
config,
|
||||
dataRef: row,
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: field.fields,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
})
|
||||
})
|
||||
if (
|
||||
!withinArrayOrBlockLocale ||
|
||||
(withinArrayOrBlockLocale && withinArrayOrBlockLocale === row._locale)
|
||||
) {
|
||||
if (row._locale) {
|
||||
delete row._locale
|
||||
}
|
||||
|
||||
acc.push(
|
||||
traverseFields<T>({
|
||||
blocks,
|
||||
config,
|
||||
dataRef: row,
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: field.fields,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
if (field.type === 'blocks') {
|
||||
const blockFieldPath = `${sanitizedPath}${field.name}`
|
||||
const blocksByPath = blocks[blockFieldPath]
|
||||
|
||||
if (Array.isArray(blocks[blockFieldPath])) {
|
||||
if (Array.isArray(blocksByPath)) {
|
||||
if (field.localized) {
|
||||
result[field.name] = {}
|
||||
|
||||
blocks[blockFieldPath].forEach((row) => {
|
||||
blocksByPath.forEach((row) => {
|
||||
if (row._uuid) {
|
||||
row.id = row._uuid
|
||||
delete row._uuid
|
||||
@@ -223,6 +245,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: locale,
|
||||
})
|
||||
|
||||
delete blockResult._order
|
||||
@@ -233,7 +256,23 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
})
|
||||
})
|
||||
} else {
|
||||
result[field.name] = blocks[blockFieldPath].map((row, i) => {
|
||||
// Add locale-specific index to have a proper blockFieldPath for current locale
|
||||
// because blocks can be in the same array for different locales!
|
||||
if (withinArrayOrBlockLocale && config.localization) {
|
||||
for (const locale of config.localization.localeCodes) {
|
||||
let localeIndex = 0
|
||||
|
||||
for (let i = 0; i < blocksByPath.length; i++) {
|
||||
const row = blocksByPath[i]
|
||||
if (row._locale === locale) {
|
||||
row._index = localeIndex
|
||||
localeIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result[field.name] = blocksByPath.reduce((acc, row, i) => {
|
||||
delete row._order
|
||||
if (row._uuid) {
|
||||
row.id = row._uuid
|
||||
@@ -242,23 +281,43 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
const block = field.blocks.find(({ slug }) => slug === row.blockType)
|
||||
|
||||
if (block) {
|
||||
return traverseFields<T>({
|
||||
blocks,
|
||||
config,
|
||||
dataRef: row,
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: block.fields,
|
||||
numbers,
|
||||
path: `${blockFieldPath}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
})
|
||||
if (
|
||||
!withinArrayOrBlockLocale ||
|
||||
(withinArrayOrBlockLocale && withinArrayOrBlockLocale === row._locale)
|
||||
) {
|
||||
if (row._locale) {
|
||||
delete row._locale
|
||||
}
|
||||
if (typeof row._index === 'number') {
|
||||
i = row._index
|
||||
delete row._index
|
||||
}
|
||||
|
||||
acc.push(
|
||||
traverseFields<T>({
|
||||
blocks,
|
||||
config,
|
||||
dataRef: row,
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: block.fields,
|
||||
numbers,
|
||||
path: `${blockFieldPath}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}),
|
||||
)
|
||||
|
||||
return acc
|
||||
}
|
||||
} else {
|
||||
acc.push({})
|
||||
}
|
||||
|
||||
return {}
|
||||
})
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,6 +364,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
field,
|
||||
ref: result,
|
||||
relations: relationPathMatch,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -339,6 +399,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
field,
|
||||
ref: result,
|
||||
textRows: textPathMatch,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -373,6 +434,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
field,
|
||||
numberRows: numberPathMatch,
|
||||
ref: result,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -391,7 +453,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
return selectResult
|
||||
}, {})
|
||||
} else {
|
||||
result[field.name] = fieldData.map(({ value }) => value)
|
||||
let selectData = fieldData
|
||||
if (withinArrayOrBlockLocale) {
|
||||
selectData = selectData.filter(({ locale }) => locale === withinArrayOrBlockLocale)
|
||||
}
|
||||
result[field.name] = selectData.map(({ value }) => value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -404,8 +470,20 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
}[] = []
|
||||
|
||||
if (field.localized && Array.isArray(table._locales)) {
|
||||
if (!table._locales.length && config.localization) {
|
||||
config.localization.localeCodes.forEach((_locale) =>
|
||||
(table._locales as unknown[]).push({ _locale }),
|
||||
)
|
||||
}
|
||||
|
||||
table._locales.forEach((localeRow) => {
|
||||
valuesToTransform.push({ ref: localizedFieldData, table: localeRow })
|
||||
valuesToTransform.push({
|
||||
ref: localizedFieldData,
|
||||
table: {
|
||||
...table,
|
||||
...localeRow,
|
||||
},
|
||||
})
|
||||
})
|
||||
} else {
|
||||
valuesToTransform.push({ ref: result, table })
|
||||
@@ -419,50 +497,28 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
case 'tab':
|
||||
case 'group': {
|
||||
const groupFieldPrefix = `${fieldPrefix || ''}${field.name}_`
|
||||
const groupData = {}
|
||||
const locale = table._locale as string
|
||||
const refKey = field.localized && locale ? locale : field.name
|
||||
|
||||
if (field.localized) {
|
||||
if (typeof locale === 'string' && !ref[locale]) {
|
||||
ref[locale] = {}
|
||||
delete table._locale
|
||||
}
|
||||
if (field.localized && locale) delete table._locale
|
||||
ref[refKey] = traverseFields<Record<string, unknown>>({
|
||||
blocks,
|
||||
config,
|
||||
dataRef: groupData as Record<string, unknown>,
|
||||
deletions,
|
||||
fieldPrefix: groupFieldPrefix,
|
||||
fields: field.fields,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}`,
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: locale || withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
Object.entries(ref).forEach(([groupLocale, groupLocaleData]) => {
|
||||
ref[groupLocale] = traverseFields<Record<string, unknown>>({
|
||||
blocks,
|
||||
config,
|
||||
dataRef: groupLocaleData as Record<string, unknown>,
|
||||
deletions,
|
||||
fieldPrefix: groupFieldPrefix,
|
||||
fields: field.fields,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}`,
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
})
|
||||
})
|
||||
if ('_order' in ref) {
|
||||
delete ref._order
|
||||
}
|
||||
} else {
|
||||
const groupData = {}
|
||||
|
||||
ref[field.name] = traverseFields<Record<string, unknown>>({
|
||||
blocks,
|
||||
config,
|
||||
dataRef: groupData as Record<string, unknown>,
|
||||
deletions,
|
||||
fieldPrefix: groupFieldPrefix,
|
||||
fields: field.fields,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}`,
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
})
|
||||
if ('_order' in ref) {
|
||||
delete ref._order
|
||||
}
|
||||
if ('_order' in ref) {
|
||||
delete ref._order
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
@@ -26,6 +26,11 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
*/
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const transformArray = ({
|
||||
@@ -43,6 +48,7 @@ export const transformArray = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
const newRows: ArrayRowToInsert[] = []
|
||||
|
||||
@@ -78,6 +84,10 @@ export const transformArray = ({
|
||||
newRow.row._locale = locale
|
||||
}
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
newRow.row._locale = withinArrayOrBlockLocale
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays: newRow.arrays,
|
||||
@@ -97,6 +107,7 @@ export const transformArray = ({
|
||||
row: newRow.row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
newRows.push(newRow)
|
||||
|
||||
@@ -26,6 +26,11 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
*/
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
export const transformBlocks = ({
|
||||
adapter,
|
||||
@@ -41,6 +46,7 @@ export const transformBlocks = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
data.forEach((blockRow, i) => {
|
||||
if (typeof blockRow.blockType !== 'string') return
|
||||
@@ -60,6 +66,7 @@ export const transformBlocks = ({
|
||||
}
|
||||
|
||||
if (field.localized && locale) newRow.row._locale = locale
|
||||
if (withinArrayOrBlockLocale) newRow.row._locale = withinArrayOrBlockLocale
|
||||
|
||||
const blockTableName = adapter.tableNameMap.get(`${baseTableName}_blocks_${blockType}`)
|
||||
|
||||
@@ -94,6 +101,7 @@ export const transformBlocks = ({
|
||||
row: newRow.row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
blocks[blockType].push(newRow)
|
||||
|
||||
@@ -58,6 +58,11 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
*/
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const traverseFields = ({
|
||||
@@ -81,6 +86,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
fields.forEach((field) => {
|
||||
let columnName = ''
|
||||
@@ -117,6 +123,7 @@ export const traverseFields = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
|
||||
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
||||
@@ -138,6 +145,7 @@ export const traverseFields = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
||||
@@ -169,6 +177,7 @@ export const traverseFields = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -187,6 +196,7 @@ export const traverseFields = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -197,6 +207,9 @@ export const traverseFields = ({
|
||||
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
|
||||
if (field.localized) {
|
||||
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
|
||||
// preserve array ID if there is
|
||||
localeData._uuid = data.id || data._uuid
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
@@ -218,9 +231,14 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// preserve array ID if there is
|
||||
const groupData = data[field.name] as Record<string, unknown>
|
||||
groupData._uuid = data.id || data._uuid
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
@@ -228,7 +246,7 @@ export const traverseFields = ({
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
columnPrefix: `${columnName}_`,
|
||||
data: data[field.name] as Record<string, unknown>,
|
||||
data: groupData,
|
||||
existingLocales,
|
||||
fieldPrefix: `${fieldName}_`,
|
||||
fields: field.fields,
|
||||
@@ -241,6 +259,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -254,6 +273,9 @@ export const traverseFields = ({
|
||||
if (typeof data[tab.name] === 'object' && data[tab.name] !== null) {
|
||||
if (tab.localized) {
|
||||
Object.entries(data[tab.name]).forEach(([localeKey, localeData]) => {
|
||||
// preserve array ID if there is
|
||||
localeData._uuid = data.id || data._uuid
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
@@ -275,9 +297,14 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const tabData = data[tab.name] as Record<string, unknown>
|
||||
// preserve array ID if there is
|
||||
tabData._uuid = data.id || data._uuid
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
@@ -285,7 +312,7 @@ export const traverseFields = ({
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
columnPrefix: `${columnPrefix || ''}${toSnakeCase(tab.name)}_`,
|
||||
data: data[tab.name] as Record<string, unknown>,
|
||||
data: tabData,
|
||||
existingLocales,
|
||||
fieldPrefix: `${fieldPrefix || ''}${tab.name}_`,
|
||||
fields: tab.fields,
|
||||
@@ -298,6 +325,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -322,6 +350,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -339,6 +368,7 @@ export const traverseFields = ({
|
||||
existingLocales,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
forcedLocale,
|
||||
locales,
|
||||
numbers,
|
||||
parentTableName,
|
||||
@@ -348,6 +378,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -384,6 +415,7 @@ export const traverseFields = ({
|
||||
|
||||
transformRelationship({
|
||||
baseRow: {
|
||||
locale: withinArrayOrBlockLocale,
|
||||
path: relationshipPath,
|
||||
},
|
||||
data: fieldData,
|
||||
@@ -416,6 +448,7 @@ export const traverseFields = ({
|
||||
} else if (Array.isArray(fieldData)) {
|
||||
transformTexts({
|
||||
baseRow: {
|
||||
locale: withinArrayOrBlockLocale,
|
||||
path: textPath,
|
||||
},
|
||||
data: fieldData,
|
||||
@@ -447,6 +480,7 @@ export const traverseFields = ({
|
||||
} else if (Array.isArray(fieldData)) {
|
||||
transformNumbers({
|
||||
baseRow: {
|
||||
locale: withinArrayOrBlockLocale,
|
||||
path: numberPath,
|
||||
},
|
||||
data: fieldData,
|
||||
@@ -479,6 +513,7 @@ export const traverseFields = ({
|
||||
const newRows = transformSelects({
|
||||
id: data._uuid || data.id,
|
||||
data: data[field.name],
|
||||
locale: withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
selects[selectTableName] = selects[selectTableName].concat(newRows)
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
PgTableWithColumns,
|
||||
PgTransaction,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import type { pgEnum } from 'drizzle-orm/pg-core'
|
||||
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
|
||||
import type { Payload } from 'payload'
|
||||
import type { BaseDatabaseAdapter } from 'payload/database'
|
||||
@@ -60,6 +61,13 @@ export type DrizzleTransaction = PgTransaction<
|
||||
ExtractTablesWithRelations<Record<string, unknown>>
|
||||
>
|
||||
|
||||
type Schema =
|
||||
| {
|
||||
enum: typeof pgEnum
|
||||
table: PgTableFn
|
||||
}
|
||||
| PgSchema
|
||||
|
||||
export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
drizzle: DrizzleDB
|
||||
enums: Record<string, GenericEnum>
|
||||
@@ -71,13 +79,13 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
idType: Args['idType']
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
pgSchema?: { table: PgTableFn } | PgSchema
|
||||
pgSchema?: Schema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
push: boolean
|
||||
relations: Record<string, GenericRelation>
|
||||
relationshipsSuffix?: string
|
||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||
schema: Record<string, unknown>
|
||||
schemaName?: Args['schemaName']
|
||||
sessions: {
|
||||
[id: string]: {
|
||||
@@ -116,7 +124,7 @@ declare module 'payload' {
|
||||
push: boolean
|
||||
relations: Record<string, GenericRelation>
|
||||
relationshipsSuffix?: string
|
||||
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
|
||||
schema: Record<string, unknown>
|
||||
sessions: {
|
||||
[id: string]: {
|
||||
db: DrizzleTransaction
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { DrizzleDB } from '../types'
|
||||
import type { PostgresAdapter } from '../types.js'
|
||||
|
||||
export const migrationTableExists = async (db: DrizzleDB): Promise<boolean> => {
|
||||
const queryRes = await db.execute(sql`SELECT to_regclass('public.payload_migrations');`)
|
||||
export const migrationTableExists = async (adapter: PostgresAdapter): Promise<boolean> => {
|
||||
let statement
|
||||
|
||||
// Returns table name 'payload_migrations' or null
|
||||
const exists = queryRes.rows?.[0]?.to_regclass === 'payload_migrations'
|
||||
return exists
|
||||
if (adapter.name === 'postgres') {
|
||||
const prependSchema = adapter.schemaName ? `"${adapter.schemaName}".` : ''
|
||||
statement = `SELECT to_regclass('${prependSchema}"payload_migrations"') AS exists;`
|
||||
}
|
||||
|
||||
const result = await adapter.drizzle.execute(sql.raw(statement))
|
||||
|
||||
const [row] = result.rows
|
||||
|
||||
return row && typeof row === 'object' && 'exists' in row && !!row.exists
|
||||
}
|
||||
|
||||
76
packages/db-postgres/src/utilities/pushDevSchema.ts
Normal file
76
packages/db-postgres/src/utilities/pushDevSchema.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { PostgresAdapter } from '../types.js'
|
||||
|
||||
import { requireDrizzleKit } from './requireDrizzleKit'
|
||||
|
||||
/**
|
||||
* Pushes the development schema to the database using Drizzle.
|
||||
*
|
||||
* @param {PostgresAdapter} adapter - The PostgresAdapter instance connected to the database.
|
||||
* @returns {Promise<void>} - A promise that resolves once the schema push is complete.
|
||||
*/
|
||||
export const pushDevSchema = async (adapter: PostgresAdapter) => {
|
||||
const { pushSchema } = requireDrizzleKit()
|
||||
|
||||
// This will prompt if clarifications are needed for Drizzle to push new schema
|
||||
const { apply, hasDataLoss, warnings } = await pushSchema(
|
||||
adapter.schema,
|
||||
adapter.drizzle,
|
||||
adapter.schemaName ? [adapter.schemaName] : undefined,
|
||||
)
|
||||
|
||||
if (warnings.length) {
|
||||
let message = `Warnings detected during schema push: \n\n${warnings.join('\n')}\n\n`
|
||||
|
||||
if (hasDataLoss) {
|
||||
message += `DATA LOSS WARNING: Possible data loss detected if schema is pushed.\n\n`
|
||||
}
|
||||
|
||||
message += `Accept warnings and push schema to database?`
|
||||
|
||||
const { confirm: acceptWarnings } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// Exit if user does not accept warnings.
|
||||
// Q: Is this the right type of exit for this interaction?
|
||||
if (!acceptWarnings) {
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
await apply()
|
||||
const migrationsTable = adapter.schemaName
|
||||
? `"${adapter.schemaName}"."payload_migrations"`
|
||||
: '"payload_migrations"'
|
||||
|
||||
const { drizzle } = adapter
|
||||
|
||||
const result = await drizzle.execute(
|
||||
sql.raw(`SELECT * FROM ${migrationsTable} WHERE batch = '-1'`),
|
||||
)
|
||||
|
||||
const devPush = result.rows
|
||||
|
||||
if (!devPush.length) {
|
||||
await drizzle.execute(
|
||||
sql.raw(`INSERT INTO ${migrationsTable} (name, batch) VALUES ('dev', '-1')`),
|
||||
)
|
||||
} else {
|
||||
await drizzle.execute(
|
||||
sql.raw(`UPDATE ${migrationsTable} SET updated_at = CURRENT_TIMESTAMP WHERE batch = '-1'`),
|
||||
)
|
||||
}
|
||||
}
|
||||
12
packages/db-postgres/src/utilities/requireDrizzleKit.ts
Normal file
12
packages/db-postgres/src/utilities/requireDrizzleKit.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { PostgresAdapter } from '../types'
|
||||
|
||||
type RequireDrizzleKit = () => {
|
||||
generateDrizzleJson: (args: { schema: Record<string, unknown> }) => unknown
|
||||
pushSchema: (
|
||||
schema: Record<string, unknown>,
|
||||
drizzle: PostgresAdapter['drizzle'],
|
||||
filterSchema?: string[],
|
||||
) => Promise<{ apply; hasDataLoss; warnings }>
|
||||
}
|
||||
|
||||
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/api')
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint-plugin-perfectionist": "2.0.0",
|
||||
"eslint-plugin-playwright": "0.16.0",
|
||||
"eslint-plugin-react": "7.33.2",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"eslint-plugin-react-hooks": "4.6.2",
|
||||
"eslint-plugin-regexp": "1.15.0"
|
||||
},
|
||||
"keywords": []
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.29.0",
|
||||
"version": "2.30.4",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
@@ -55,13 +55,13 @@
|
||||
"@date-io/date-fns": "2.16.0",
|
||||
"@dnd-kit/core": "6.0.8",
|
||||
"@dnd-kit/sortable": "7.0.2",
|
||||
"@faceless-ui/modal": "2.0.1",
|
||||
"@faceless-ui/modal": "2.0.2",
|
||||
"@faceless-ui/scroll-info": "1.3.0",
|
||||
"@faceless-ui/window-info": "2.1.1",
|
||||
"@faceless-ui/window-info": "2.1.2",
|
||||
"@monaco-editor/react": "4.5.1",
|
||||
"@swc/core": "1.6.1",
|
||||
"@swc/register": "0.1.10",
|
||||
"body-parser": "1.20.2",
|
||||
"body-parser": "1.20.3",
|
||||
"body-scroll-lock": "4.0.0-beta.0",
|
||||
"bson-objectid": "2.0.4",
|
||||
"compression": "1.7.4",
|
||||
@@ -70,7 +70,7 @@
|
||||
"console-table-printer": "2.11.2",
|
||||
"dataloader": "2.2.2",
|
||||
"date-fns": "2.30.0",
|
||||
"deep-equal": "2.2.2",
|
||||
"deep-equal": "2.2.3",
|
||||
"deepmerge": "4.3.1",
|
||||
"dotenv": "8.6.0",
|
||||
"express": "4.21.0",
|
||||
@@ -97,14 +97,14 @@
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
"joi": "17.9.2",
|
||||
"json-schema-to-typescript": "14.0.5",
|
||||
"jsonwebtoken": "9.0.1",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"jwt-decode": "3.1.2",
|
||||
"md5": "2.3.0",
|
||||
"method-override": "3.0.0",
|
||||
"minimist": "1.2.8",
|
||||
"mkdirp": "1.0.4",
|
||||
"monaco-editor": "0.38.0",
|
||||
"nodemailer": "6.9.8",
|
||||
"nodemailer": "6.9.15",
|
||||
"object-to-formdata": "4.5.1",
|
||||
"passport": "0.6.0",
|
||||
"passport-anonymous": "1.0.1",
|
||||
@@ -132,11 +132,11 @@
|
||||
"react-toastify": "10.0.5",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sass": "1.69.4",
|
||||
"scheduler": "0.23.0",
|
||||
"scheduler": "0.23.2",
|
||||
"scmp": "2.1.0",
|
||||
"sharp": "0.32.6",
|
||||
"swc-loader": "0.2.3",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"swc-loader": "0.2.6",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"ts-essentials": "7.0.3",
|
||||
"use-context-selector": "1.4.1",
|
||||
"uuid": "9.0.1"
|
||||
@@ -145,7 +145,7 @@
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@release-it/conventional-changelog": "7.0.0",
|
||||
"@types/asap": "2.0.0",
|
||||
"@types/body-parser": "1.19.2",
|
||||
"@types/body-parser": "1.19.5",
|
||||
"@types/body-scroll-lock": "^3.1.0",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "4.17.17",
|
||||
@@ -158,14 +158,14 @@
|
||||
"@types/isomorphic-fetch": "0.0.36",
|
||||
"@types/joi": "14.3.4",
|
||||
"@types/json-schema": "7.0.12",
|
||||
"@types/jsonwebtoken": "8.5.9",
|
||||
"@types/jsonwebtoken": "9.0.7",
|
||||
"@types/method-override": "0.0.32",
|
||||
"@types/mime": "2.0.3",
|
||||
"@types/mini-css-extract-plugin": "^1.4.3",
|
||||
"@types/minimist": "1.2.2",
|
||||
"@types/mkdirp": "1.0.2",
|
||||
"@types/node-fetch": "2.6.4",
|
||||
"@types/nodemailer": "6.4.14",
|
||||
"@types/nodemailer": "6.4.16",
|
||||
"@types/passport": "1.0.12",
|
||||
"@types/passport-anonymous": "1.0.3",
|
||||
"@types/passport-jwt": "3.0.9",
|
||||
@@ -202,9 +202,9 @@
|
||||
"rimraf": "4.4.1",
|
||||
"sass-loader": "12.6.0",
|
||||
"serve-static": "1.15.0",
|
||||
"swc-loader": "^0.2.3",
|
||||
"swc-loader": "^0.2.6",
|
||||
"terser": "5.19.2",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"url-loader": "4.1.1",
|
||||
"vite": "^4.4.9",
|
||||
"webpack": "^5.78.0"
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import ObjectID from 'bson-objectid'
|
||||
|
||||
import { type BeforeDuplicate, type Field, tabHasName } from '../../../../exports/types'
|
||||
|
||||
/**
|
||||
* Creates new IDs for blocks / arrays items to avoid errors with relational databases.
|
||||
*/
|
||||
export const baseBeforeDuplicate = (args: Parameters<BeforeDuplicate>[0]) => {
|
||||
const {
|
||||
collection: { fields },
|
||||
data,
|
||||
} = args
|
||||
|
||||
traverseFields(fields, data)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
function traverseFields(fields: Field[], data: unknown) {
|
||||
if (typeof data === 'undefined' || data === null) return
|
||||
|
||||
fields.forEach((field) => {
|
||||
switch (field.type) {
|
||||
case 'array':
|
||||
if (Array.isArray(data?.[field.name])) {
|
||||
data[field.name].forEach((row) => {
|
||||
if (!row) return
|
||||
row.id = new ObjectID().toHexString()
|
||||
traverseFields(field.fields, row)
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'blocks': {
|
||||
if (Array.isArray(data?.[field.name])) {
|
||||
data[field.name].forEach((row) => {
|
||||
if (!row) return
|
||||
const configBlock = field.blocks.find((block) => block.slug === row.blockType)
|
||||
if (!configBlock) return
|
||||
row.id = new ObjectID().toHexString()
|
||||
traverseFields(configBlock.fields, row)
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'row':
|
||||
case 'collapsible':
|
||||
traverseFields(field.fields, data)
|
||||
break
|
||||
case 'tabs':
|
||||
field.tabs.forEach((tab) => {
|
||||
if (!tabHasName(tab)) {
|
||||
traverseFields(tab.fields, data)
|
||||
return
|
||||
}
|
||||
|
||||
if (data && data[tab.name]) {
|
||||
traverseFields(tab.fields, data[tab.name])
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'group':
|
||||
if (data && data[field.name]) {
|
||||
traverseFields(field.fields, data[field.name])
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import MinimalTemplate from '../../templates/Minimal'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import Button from '../Button'
|
||||
import * as PopupList from '../Popup/PopupButtonList'
|
||||
import { baseBeforeDuplicate } from './baseBeforeDuplicate'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'duplicate'
|
||||
@@ -65,6 +66,8 @@ const Duplicate: React.FC<Props> = ({ id, slug, collection }) => {
|
||||
})
|
||||
let data = await response.json()
|
||||
|
||||
data = baseBeforeDuplicate({ collection, data, locale })
|
||||
|
||||
if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
|
||||
data = await collection.admin.hooks.beforeDuplicate({
|
||||
collection,
|
||||
@@ -73,6 +76,8 @@ const Duplicate: React.FC<Props> = ({ id, slug, collection }) => {
|
||||
})
|
||||
}
|
||||
|
||||
delete data['id']
|
||||
|
||||
if (!duplicateID) {
|
||||
if ('createdAt' in data) delete data.createdAt
|
||||
if ('updatedAt' in data) delete data.updatedAt
|
||||
|
||||
@@ -77,7 +77,7 @@ const SaveDraft: React.FC<{ action: string; disabled: boolean }> = ({ action, di
|
||||
}, [action, submit])
|
||||
|
||||
return (
|
||||
<FormSubmit className={`${baseClass}__draft`} disabled={disabled} onClick={save}>
|
||||
<FormSubmit buttonStyle="secondary" className={`${baseClass}__draft`} disabled={disabled} onClick={save}>
|
||||
{t('saveDraft')}
|
||||
</FormSubmit>
|
||||
)
|
||||
@@ -147,11 +147,11 @@ const EditMany: React.FC<Props> = (props) => {
|
||||
<div className={`${baseClass}__document-actions`}>
|
||||
{collection.versions ? (
|
||||
<React.Fragment>
|
||||
<Publish
|
||||
<SaveDraft
|
||||
action={`${serverURL}${api}/${slug}${getQueryParams()}&draft=true`}
|
||||
disabled={selected.length === 0}
|
||||
/>
|
||||
<SaveDraft
|
||||
<Publish
|
||||
action={`${serverURL}${api}/${slug}${getQueryParams()}&draft=true`}
|
||||
disabled={selected.length === 0}
|
||||
/>
|
||||
|
||||
@@ -228,6 +228,15 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
|
||||
const duplicateRowState = deepCopyObject(rows[rowIndex])
|
||||
if (duplicateRowState.id) duplicateRowState.id = new ObjectID().toHexString()
|
||||
|
||||
for (const key of Object.keys(duplicateRowState).filter((key) => key.endsWith('.id'))) {
|
||||
const idState = duplicateRowState[key]
|
||||
|
||||
if (idState && typeof idState.value === 'string' && ObjectID.isValid(idState.value)) {
|
||||
duplicateRowState[key].value = new ObjectID().toHexString()
|
||||
duplicateRowState[key].initialValue = new ObjectID().toHexString()
|
||||
}
|
||||
}
|
||||
|
||||
// If there are subfields
|
||||
if (Object.keys(duplicateRowState).length > 0) {
|
||||
// Add new object containing subfield names to unflattenedRows array
|
||||
|
||||
@@ -48,6 +48,11 @@ const RelationshipCell: React.FC<CellComponentProps<RelationshipField | UploadFi
|
||||
relationTo: field.relationTo,
|
||||
value: cell,
|
||||
})
|
||||
} else if (typeof cell.id !== 'undefined' && typeof field.relationTo === 'string') {
|
||||
formattedValues.push({
|
||||
relationTo: field.relationTo,
|
||||
value: cell.id,
|
||||
})
|
||||
}
|
||||
})
|
||||
getRelationships(formattedValues)
|
||||
|
||||
@@ -74,6 +74,19 @@ export async function validateSearchParam({
|
||||
})
|
||||
}
|
||||
const promises = []
|
||||
|
||||
// Sanitize relation.otherRelation.id to relation.otherRelation
|
||||
if (paths.at(-1)?.path === 'id') {
|
||||
const previousField = paths.at(-2)?.field
|
||||
if (
|
||||
previousField &&
|
||||
(previousField.type === 'relationship' || previousField.type === 'upload') &&
|
||||
typeof previousField.relationTo === 'string'
|
||||
) {
|
||||
paths.pop()
|
||||
}
|
||||
}
|
||||
|
||||
promises.push(
|
||||
...paths.map(async ({ collectionSlug, field, invalid, path }, i) => {
|
||||
if (invalid) {
|
||||
@@ -110,6 +123,7 @@ export async function validateSearchParam({
|
||||
if (field.type === 'relationship' && Array.isArray(field.relationTo)) {
|
||||
fieldPath = fieldPath.replace('.value', '')
|
||||
}
|
||||
|
||||
const entityType: 'collections' | 'globals' = globalConfig ? 'globals' : 'collections'
|
||||
const entitySlug = collectionSlug || globalConfig.slug
|
||||
const segments = fieldPath.split('.')
|
||||
|
||||
@@ -23,11 +23,13 @@ export {
|
||||
useListDrawer,
|
||||
} from '../../admin/components/elements/ListDrawer'
|
||||
|
||||
export { useNav } from '../../admin/components/elements/Nav/context'
|
||||
|
||||
export { default as NavGroup } from '../../admin/components/elements/NavGroup'
|
||||
export {
|
||||
Description,
|
||||
DescriptionComponent,
|
||||
DescriptionFunction,
|
||||
} from '../../admin/components/forms/FieldDescription/types'
|
||||
|
||||
export { useNav } from '../../admin/components/elements/Nav/context'
|
||||
export { default as NavGroup } from '../../admin/components/elements/NavGroup'
|
||||
export { toast } from 'react-toastify'
|
||||
|
||||
@@ -228,23 +228,58 @@ export const generateFileData = async <T>({
|
||||
withMetadata,
|
||||
})
|
||||
|
||||
filesToSave.push({
|
||||
buffer: croppedImage,
|
||||
path: `${staticPath}/${fsSafeName}`,
|
||||
})
|
||||
// Apply resize after cropping to ensure it conforms to resizeOptions
|
||||
if (resizeOptions) {
|
||||
const resizedAfterCrop = await sharp(croppedImage)
|
||||
.resize({
|
||||
fit: resizeOptions?.fit || 'cover',
|
||||
height: resizeOptions?.height,
|
||||
position: resizeOptions?.position || 'center',
|
||||
width: resizeOptions?.width,
|
||||
})
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
|
||||
fileForResize = {
|
||||
...file,
|
||||
data: croppedImage,
|
||||
size: info.size,
|
||||
filesToSave.push({
|
||||
buffer: resizedAfterCrop.data,
|
||||
path: `${staticPath}/${fsSafeName}`,
|
||||
})
|
||||
|
||||
fileForResize = {
|
||||
...fileForResize,
|
||||
data: resizedAfterCrop.data,
|
||||
size: resizedAfterCrop.info.size,
|
||||
}
|
||||
|
||||
fileData.width = resizedAfterCrop.info.width
|
||||
fileData.height = resizedAfterCrop.info.height
|
||||
if (fileIsAnimatedType) {
|
||||
const metadata = await sharpFile.metadata()
|
||||
fileData.height = metadata.pages
|
||||
? resizedAfterCrop.info.height / metadata.pages
|
||||
: resizedAfterCrop.info.height
|
||||
}
|
||||
fileData.filesize = resizedAfterCrop.info.size
|
||||
} else {
|
||||
// If resizeOptions is not present, just save the cropped image
|
||||
filesToSave.push({
|
||||
buffer: croppedImage,
|
||||
path: `${staticPath}/${fsSafeName}`,
|
||||
})
|
||||
|
||||
fileForResize = {
|
||||
...file,
|
||||
data: croppedImage,
|
||||
size: info.size,
|
||||
}
|
||||
|
||||
fileData.width = info.width
|
||||
fileData.height = info.height
|
||||
if (fileIsAnimatedType) {
|
||||
const metadata = await sharpFile.metadata()
|
||||
fileData.height = metadata.pages ? info.height / metadata.pages : info.height
|
||||
}
|
||||
fileData.filesize = info.size
|
||||
}
|
||||
fileData.width = info.width
|
||||
fileData.height = info.height
|
||||
if (fileIsAnimatedType) {
|
||||
const metadata = await sharpFile.metadata()
|
||||
fileData.height = metadata.pages ? info.height / metadata.pages : info.height
|
||||
}
|
||||
fileData.filesize = info.size
|
||||
|
||||
if (file.tempFilePath) {
|
||||
await fs.promises.writeFile(file.tempFilePath, croppedImage) // write fileBuffer to the temp path
|
||||
|
||||
@@ -191,31 +191,6 @@ const getImageResizeAction = ({
|
||||
return hasFocalPoint ? 'resizeWithFocalPoint' : 'resize'
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the image should be passed directly to sharp without payload adjusting properties.
|
||||
*
|
||||
* @param resizeConfig - object containing the requested dimensions and resize options
|
||||
* @param original - the original image size
|
||||
* @returns true if the image should passed directly to sharp
|
||||
*/
|
||||
const applyPayloadAdjustments = (
|
||||
{ fit, height, width, withoutEnlargement, withoutReduction }: ImageSize,
|
||||
original: ProbedImageSize,
|
||||
) => {
|
||||
if (fit === 'contain' || fit === 'inside') return false
|
||||
if (!isNumber(height) && !isNumber(width)) return false
|
||||
|
||||
const targetAspectRatio = width / height
|
||||
const originalAspectRatio = original.width / original.height
|
||||
if (originalAspectRatio === targetAspectRatio) return false
|
||||
|
||||
const skipEnlargement = withoutEnlargement && (original.height < height || original.width < width)
|
||||
const skipReduction = withoutReduction && (original.height > height || original.width > width)
|
||||
if (skipEnlargement || skipReduction) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the resize config. If the resize config has the `withoutReduction`
|
||||
* property set to true, the `fit` and `position` properties will be set to `contain`
|
||||
@@ -302,6 +277,18 @@ export default async function resizeAndTransformImageSizes({
|
||||
const sharpBase: Sharp | undefined = sharp(file.tempFilePath || file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
||||
const originalImageMeta = await sharpBase.metadata()
|
||||
|
||||
let adjustedDimensions = { ...dimensions }
|
||||
|
||||
// Images with an exif orientation of 5, 6, 7, or 8 are auto-rotated by sharp
|
||||
// Need to adjust the dimensions to match the original image
|
||||
if ([5, 6, 7, 8].includes(originalImageMeta.orientation)) {
|
||||
adjustedDimensions = {
|
||||
...dimensions,
|
||||
height: dimensions.width,
|
||||
width: dimensions.height,
|
||||
}
|
||||
}
|
||||
|
||||
const resizeImageMeta = {
|
||||
height: extractHeightFromImage(originalImageMeta),
|
||||
width: originalImageMeta.width,
|
||||
@@ -324,7 +311,7 @@ export default async function resizeAndTransformImageSizes({
|
||||
if (resizeAction === 'resizeWithFocalPoint') {
|
||||
let { height: resizeHeight, width: resizeWidth } = imageResizeConfig
|
||||
|
||||
const originalAspectRatio = dimensions.width / dimensions.height
|
||||
const originalAspectRatio = adjustedDimensions.width / adjustedDimensions.height
|
||||
|
||||
// Calculate resizeWidth based on original aspect ratio if it's undefined
|
||||
if (resizeHeight && !resizeWidth) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Where } from '../../types'
|
||||
|
||||
export const appendVersionToQueryKey = (query: Where): Where => {
|
||||
export const appendVersionToQueryKey = (query: Where = {}): Where => {
|
||||
return Object.entries(query).reduce((res, [key, val]) => {
|
||||
if (['AND', 'OR', 'and', 'or'].includes(key) && Array.isArray(val)) {
|
||||
return {
|
||||
|
||||
@@ -13,5 +13,9 @@ export const getQueryDraftsSort = (sort: string): string => {
|
||||
orderBy = sort.substring(1)
|
||||
}
|
||||
|
||||
if (orderBy === 'id') {
|
||||
return `${direction}parent`
|
||||
}
|
||||
|
||||
return `${direction}version.${orderBy}`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import type { Payload } from '../payload'
|
||||
import type { PayloadRequest } from '../types'
|
||||
import type { TypeWithVersion } from './types'
|
||||
|
||||
import { combineQueries } from '../database/combineQueries'
|
||||
import { docHasTimestamps } from '../types'
|
||||
import { appendVersionToQueryKey } from './drafts/appendVersionToQueryKey'
|
||||
|
||||
type Args = {
|
||||
config: SanitizedCollectionConfig
|
||||
@@ -32,7 +34,7 @@ export const getLatestCollectionVersion = async <T extends TypeWithID = any>({
|
||||
pagination: false,
|
||||
req,
|
||||
sort: '-updatedAt',
|
||||
where: { parent: { equals: id } },
|
||||
where: combineQueries(appendVersionToQueryKey(query.where), { parent: { equals: id } }),
|
||||
})
|
||||
;[latestVersion] = docs
|
||||
}
|
||||
|
||||
@@ -96,11 +96,19 @@ From there, create the adapter, passing in all of its required properties:
|
||||
```js
|
||||
import { azureBlobStorageAdapter } from '@payloadcms/plugin-cloud-storage/azure'
|
||||
|
||||
// if you need to obtain credentials you may do so by following the instructions here: https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad-app?tabs=javascript
|
||||
// or you can use the connection string directly.
|
||||
|
||||
const adapter = azureBlobStorageAdapter({
|
||||
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
|
||||
containerName: process.env.AZURE_STORAGE_CONTAINER_NAME,
|
||||
allowContainerCreate: process.env.AZURE_STORAGE_ALLOW_CONTAINER_CREATE === 'true',
|
||||
baseURL: process.env.AZURE_STORAGE_ACCOUNT_BASEURL,
|
||||
/**
|
||||
* Optional: You may wish to obtain credentials that cannot be passed through in the connectionString connection option. In that case the connectionString will only be the URL to the storage account.
|
||||
* Can be one of AnonymousCredential | StorageSharedKeyCredential | TokenCredential
|
||||
**/
|
||||
credentials: new StorageSharedKeyCredential(process.env.AZURE_STORAGE_ACCOUNT_NAME, process.env.AZURE_STORAGE_ACCOUNT_KEY),
|
||||
})
|
||||
|
||||
// Now you can pass this adapter to the plugin
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
@@ -53,6 +53,7 @@
|
||||
"@aws-sdk/client-s3": "^3.142.0",
|
||||
"@aws-sdk/lib-storage": "^3.267.0",
|
||||
"@azure/storage-blob": "^12.11.0",
|
||||
"@azure/core-http": "^3.0.0",
|
||||
"@google-cloud/storage": "^6.4.1",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/find-node-modules": "^2.1.2",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { ContainerClient } from '@azure/storage-blob'
|
||||
import type { TokenCredential } from '@azure/core-http'
|
||||
import type {
|
||||
AnonymousCredential,
|
||||
ContainerClient,
|
||||
StorageSharedKeyCredential,
|
||||
} from '@azure/storage-blob'
|
||||
|
||||
import { BlobServiceClient } from '@azure/storage-blob'
|
||||
|
||||
@@ -15,6 +20,7 @@ export interface Args {
|
||||
baseURL: string
|
||||
connectionString: string
|
||||
containerName: string
|
||||
credential?: AnonymousCredential | StorageSharedKeyCredential | TokenCredential
|
||||
}
|
||||
|
||||
export const azureBlobStorageAdapter = ({
|
||||
@@ -22,11 +28,14 @@ export const azureBlobStorageAdapter = ({
|
||||
baseURL,
|
||||
connectionString,
|
||||
containerName,
|
||||
credential,
|
||||
}: Args): Adapter => {
|
||||
let storageClient: ContainerClient | null = null
|
||||
const getStorageClient = () => {
|
||||
if (storageClient) return storageClient
|
||||
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString)
|
||||
const blobServiceClient = credential
|
||||
? new BlobServiceClient(connectionString, credential)
|
||||
: BlobServiceClient.fromConnectionString(connectionString)
|
||||
return (storageClient = blobServiceClient.getContainerClient(containerName))
|
||||
}
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@
|
||||
"@aws-sdk/credential-providers": "^3.289.0",
|
||||
"@aws-sdk/lib-storage": "^3.267.0",
|
||||
"amazon-cognito-identity-js": "^6.1.2",
|
||||
"nodemailer": "6.9.9"
|
||||
"nodemailer": "6.9.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/nodemailer": "6.4.14",
|
||||
"@types/nodemailer": "6.4.16",
|
||||
"payload": "workspace:*",
|
||||
"ts-jest": "^29.1.0",
|
||||
"webpack": "^5.78.0"
|
||||
|
||||
@@ -4,6 +4,7 @@ import fa from './fa.json'
|
||||
import fr from './fr.json'
|
||||
import nb from './nb.json'
|
||||
import pl from './pl.json'
|
||||
import tr from './tr.json'
|
||||
import ua from './ua.json'
|
||||
import zh from './zh.json'
|
||||
import zhTw from './zh-tw.json'
|
||||
@@ -14,7 +15,8 @@ export default {
|
||||
fa,
|
||||
fr,
|
||||
nb,
|
||||
pl,
|
||||
pl,
|
||||
tr,
|
||||
ua,
|
||||
zh,
|
||||
zhTw,
|
||||
|
||||
23
packages/plugin-seo/src/translations/tr.json
Normal file
23
packages/plugin-seo/src/translations/tr.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "./translation-schema.json",
|
||||
"plugin-seo": {
|
||||
"autoGenerate": "Otomatik oluştur",
|
||||
"imageAutoGenerationTip": "Otomatik oluşturma, seçilen ana görseli alacaktır.",
|
||||
"bestPractices": "en iyi uygulamalar",
|
||||
"lengthTipTitle": "{{minLength}} ile {{maxLength}} karakter arasında olmalıdır. Kaliteli meta başlıkları yazmak için yardım almak isterseniz, bkz. ",
|
||||
"lengthTipDescription": "{{minLength}} ile {{maxLength}} karakter arasında olmalıdır. Kaliteli meta açıklamaları yazmak için yardım almak isterseniz, bkz. ",
|
||||
"good": "İyi",
|
||||
"tooLong": "Çok uzun",
|
||||
"tooShort": "Çok kısa",
|
||||
"almostThere": "Neredeyse tamam",
|
||||
"characterCount": "{{current}}/{{minLength}}-{{maxLength}} karakter, ",
|
||||
"charactersToGo": "{{characters}} karakter kaldı",
|
||||
"charactersLeftOver": "{{characters}} karakter kaldı",
|
||||
"charactersTooMany": "{{characters}} karakter fazla",
|
||||
"noImage": "Görsel yok",
|
||||
"checksPassing": "{{current}}/{{max}} kontrol geçiyor",
|
||||
"preview": "Önizleme",
|
||||
"previewDescription": "Kesin sonuç listeleri içeriğe ve arama alaka düzeyine bağlı olarak değişebilir."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "0.11.3",
|
||||
"version": "0.11.4",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -22,7 +22,7 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faceless-ui/modal": "2.0.1",
|
||||
"@faceless-ui/modal": "2.0.2",
|
||||
"@lexical/headless": "0.13.1",
|
||||
"@lexical/link": "0.13.1",
|
||||
"@lexical/list": "0.13.1",
|
||||
@@ -39,7 +39,7 @@
|
||||
"json-schema": "^0.4.0",
|
||||
"lexical": "0.13.1",
|
||||
"lodash": "4.17.21",
|
||||
"react-error-boundary": "4.0.12",
|
||||
"react-error-boundary": "4.0.13",
|
||||
"react-i18next": "11.18.6",
|
||||
"ts-essentials": "7.0.3"
|
||||
},
|
||||
|
||||
@@ -118,13 +118,14 @@ export const LinkFeature = (props: LinkFeatureProps): FeatureProvider => {
|
||||
})
|
||||
|
||||
const rel: string = node.fields.newTab ? ' rel="noopener noreferrer"' : ''
|
||||
const target: string = node.fields.newTab ? ' target="_blank"' : ''
|
||||
|
||||
const href: string =
|
||||
node.fields.linkType === 'custom'
|
||||
? node.fields.url
|
||||
: (node.fields.doc?.value as string)
|
||||
|
||||
return `<a href="${href}"${rel}>${childrenText}</a>`
|
||||
return `<a href="${href}"${target}${rel}>${childrenText}</a>`
|
||||
},
|
||||
nodeTypes: [LinkNode.getType()],
|
||||
} as HTMLConverter<SerializedLinkNode>,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faceless-ui/modal": "2.0.1",
|
||||
"@faceless-ui/modal": "2.0.2",
|
||||
"i18next": "22.5.1",
|
||||
"is-hotkey": "0.2.0",
|
||||
"react-i18next": "11.18.6",
|
||||
|
||||
1437
pnpm-lock.yaml
generated
1437
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -177,7 +177,7 @@ If you are migrating an existing site or moving content to a new URL, you can us
|
||||
|
||||
## Website
|
||||
|
||||
This template includes a beautifully designed, production-ready front-end built with the [Next.js App Router](https://nextjs.org), served right alongside your Payload app in a single Express server. This makes is so that you can deploy both apps simultaneously and host them together. If you prefer a different front-end framework, this pattern works for any framework that supports a custom server. If you prefer to host your website separately from Payload, you can easily [Eject](#eject) the front-end out from this template to swap in your own, or to use it as a standalone CMS. For more details, see the official [Custom Server Example](https://github.com/payloadcms/payload/tree/main/examples/custom-server).
|
||||
This template includes a beautifully designed, production-ready front-end built with the [Next.js App Router](https://nextjs.org), served right alongside your Payload app in a single Express server. This makes it so that you can deploy both apps simultaneously and host them together. If you prefer a different front-end framework, this pattern works for any framework that supports a custom server. If you prefer to host your website separately from Payload, you can easily [Eject](#eject) the front-end out from this template to swap in your own, or to use it as a standalone CMS. For more details, see the official [Custom Server Example](https://github.com/payloadcms/payload/tree/main/examples/custom-server).
|
||||
|
||||
Core features:
|
||||
|
||||
|
||||
@@ -877,6 +877,20 @@ describe('collections-rest', () => {
|
||||
expect(result.totalDocs).toEqual(1)
|
||||
})
|
||||
|
||||
it('like - id should not crash', async () => {
|
||||
await createPost({ title: 'post' })
|
||||
|
||||
const response = await client.find({
|
||||
query: {
|
||||
id: {
|
||||
like: 'words partial',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
})
|
||||
|
||||
it('exists - true', async () => {
|
||||
const postWithDesc = await createPost({ description: 'exists' })
|
||||
await createPost({ description: undefined })
|
||||
|
||||
3
test/database/.gitignore
vendored
3
test/database/.gitignore
vendored
@@ -1 +1,4 @@
|
||||
migrations
|
||||
v5_migrations/*
|
||||
!v5_migrations/20241018_162142_test_v5.ts
|
||||
!v5_migrations/20241018_162142_test_v5.json
|
||||
@@ -59,7 +59,7 @@ describe('database', () => {
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
removeFiles(path.normalize(payload.db.migrationDir))
|
||||
removeFiles(path.normalize(payload.db.migrationDir), (name) => !name.includes('test_v5'))
|
||||
})
|
||||
|
||||
it('should run migrate:create', async () => {
|
||||
@@ -74,6 +74,32 @@ describe('database', () => {
|
||||
expect(migrationFile).toContain('_test')
|
||||
})
|
||||
|
||||
it('should run migrate:create with older drizzle version schema', async () => {
|
||||
const db = payload.db as unknown as PostgresAdapter
|
||||
|
||||
// eslint-disable-next-line jest/no-if
|
||||
if (db.name !== 'postgres') return
|
||||
|
||||
// eslint-disable-next-line jest/no-if
|
||||
if (db.schemaName && db.schemaName !== 'public') {
|
||||
return
|
||||
}
|
||||
|
||||
const args = {
|
||||
_: ['migrate:create', 'test'],
|
||||
forceAcceptWarning: true,
|
||||
}
|
||||
const ogMigrationDir = payload.db.migrationDir
|
||||
payload.db.migrationDir = path.resolve(__dirname, 'v5_migrations')
|
||||
await migrate(args)
|
||||
|
||||
// read files names in migrationsDir
|
||||
const migrationFile = path.normalize(fs.readdirSync(payload.db.migrationDir)[2])
|
||||
expect(migrationFile).toContain('_test')
|
||||
removeFiles(path.normalize(payload.db.migrationDir), (name) => !name.includes('test_v5'))
|
||||
payload.db.migrationDir = ogMigrationDir
|
||||
})
|
||||
|
||||
it('should run migrate', async () => {
|
||||
const args = {
|
||||
_: ['migrate'],
|
||||
|
||||
1866
test/database/v5_migrations/20241018_162142_test_v5.json
Normal file
1866
test/database/v5_migrations/20241018_162142_test_v5.json
Normal file
File diff suppressed because it is too large
Load Diff
476
test/database/v5_migrations/20241018_162142_test_v5.ts
Normal file
476
test/database/v5_migrations/20241018_162142_test_v5.ts
Normal file
@@ -0,0 +1,476 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-postgres'
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
||||
await payload.db.drizzle.execute(sql`
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "_locales" AS ENUM('en', 'es');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "selectEnum" AS ENUM('a', 'b', 'c');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "radioEnum" AS ENUM('a', 'b', 'c');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "enum_customs_status" AS ENUM('draft', 'published');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "enum__customs_v_version_status" AS ENUM('draft', 'published');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "posts" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"throw_after_change" boolean,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "relation_a" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"rich_text" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "relation_a_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"relation_b_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "relation_b" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"rich_text" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "relation_b_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"relation_a_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customs_customSelect" (
|
||||
"order" integer NOT NULL,
|
||||
"parent_id" integer NOT NULL,
|
||||
"value" "selectEnum",
|
||||
"id" serial PRIMARY KEY NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customArrays" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"text" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customArrays_locales" (
|
||||
"localized_text" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL,
|
||||
CONSTRAINT "customArrays_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customBlocks" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"text" varchar,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customBlocks_locales" (
|
||||
"localized_text" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL,
|
||||
CONSTRAINT "customBlocks_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customs" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"text" varchar,
|
||||
"radio" "radioEnum",
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"_status" "enum_customs_status"
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customs_locales" (
|
||||
"localized_text" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
CONSTRAINT "customs_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customs_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"relation_a_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customs_v_version_customSelect" (
|
||||
"order" integer NOT NULL,
|
||||
"parent_id" integer NOT NULL,
|
||||
"value" "selectEnum",
|
||||
"id" serial PRIMARY KEY NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customArrays_v" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"text" varchar,
|
||||
"_uuid" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customArrays_v_locales" (
|
||||
"localized_text" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
CONSTRAINT "_customArrays_v_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customBlocks_v" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"text" varchar,
|
||||
"_uuid" varchar,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customBlocks_v_locales" (
|
||||
"localized_text" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
CONSTRAINT "_customBlocks_v_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customs_v" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"version_text" varchar,
|
||||
"version_radio" "radioEnum",
|
||||
"version_updated_at" timestamp(3) with time zone,
|
||||
"version_created_at" timestamp(3) with time zone,
|
||||
"version__status" "enum__customs_v_version_status",
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"latest" boolean
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customs_v_locales" (
|
||||
"version_localized_text" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
CONSTRAINT "_customs_v_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customs_v_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"customs_id" integer,
|
||||
"relation_a_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"email" varchar NOT NULL,
|
||||
"reset_password_token" varchar,
|
||||
"reset_password_expiration" timestamp(3) with time zone,
|
||||
"salt" varchar,
|
||||
"hash" varchar,
|
||||
"login_attempts" numeric,
|
||||
"lock_until" timestamp(3) with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payload_preferences" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"key" varchar,
|
||||
"value" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payload_preferences_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"users_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payload_migrations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar,
|
||||
"batch" numeric,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "customGlobal" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"text" varchar,
|
||||
"updated_at" timestamp(3) with time zone,
|
||||
"created_at" timestamp(3) with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "_customGlobal_v" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"version_text" varchar,
|
||||
"version_updated_at" timestamp(3) with time zone,
|
||||
"version_created_at" timestamp(3) with time zone,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "posts_created_at_idx" ON "posts" ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "relation_a_created_at_idx" ON "relation_a" ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "relation_a_rels_order_idx" ON "relation_a_rels" ("order");
|
||||
CREATE INDEX IF NOT EXISTS "relation_a_rels_parent_idx" ON "relation_a_rels" ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "relation_a_rels_path_idx" ON "relation_a_rels" ("path");
|
||||
CREATE INDEX IF NOT EXISTS "relation_b_created_at_idx" ON "relation_b" ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "relation_b_rels_order_idx" ON "relation_b_rels" ("order");
|
||||
CREATE INDEX IF NOT EXISTS "relation_b_rels_parent_idx" ON "relation_b_rels" ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "relation_b_rels_path_idx" ON "relation_b_rels" ("path");
|
||||
CREATE INDEX IF NOT EXISTS "customs_customSelect_order_idx" ON "customs_customSelect" ("order");
|
||||
CREATE INDEX IF NOT EXISTS "customs_customSelect_parent_idx" ON "customs_customSelect" ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "customArrays_order_idx" ON "customArrays" ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "customArrays_parent_id_idx" ON "customArrays" ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "customBlocks_order_idx" ON "customBlocks" ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "customBlocks_parent_id_idx" ON "customBlocks" ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "customBlocks_path_idx" ON "customBlocks" ("_path");
|
||||
CREATE INDEX IF NOT EXISTS "customs_created_at_idx" ON "customs" ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "customs__status_idx" ON "customs" ("_status");
|
||||
CREATE INDEX IF NOT EXISTS "customs_rels_order_idx" ON "customs_rels" ("order");
|
||||
CREATE INDEX IF NOT EXISTS "customs_rels_parent_idx" ON "customs_rels" ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "customs_rels_path_idx" ON "customs_rels" ("path");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_version_customSelect_order_idx" ON "_customs_v_version_customSelect" ("order");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_version_customSelect_parent_idx" ON "_customs_v_version_customSelect" ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "_customArrays_v_order_idx" ON "_customArrays_v" ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "_customArrays_v_parent_id_idx" ON "_customArrays_v" ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "_customBlocks_v_order_idx" ON "_customBlocks_v" ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "_customBlocks_v_parent_id_idx" ON "_customBlocks_v" ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "_customBlocks_v_path_idx" ON "_customBlocks_v" ("_path");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_version_version_created_at_idx" ON "_customs_v" ("version_created_at");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_version_version__status_idx" ON "_customs_v" ("version__status");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_created_at_idx" ON "_customs_v" ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_updated_at_idx" ON "_customs_v" ("updated_at");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_latest_idx" ON "_customs_v" ("latest");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_rels_order_idx" ON "_customs_v_rels" ("order");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_rels_parent_idx" ON "_customs_v_rels" ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "_customs_v_rels_path_idx" ON "_customs_v_rels" ("path");
|
||||
CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" ("created_at");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_key_idx" ON "payload_preferences" ("key");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_created_at_idx" ON "payload_preferences" ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_order_idx" ON "payload_preferences_rels" ("order");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_path_idx" ON "payload_preferences_rels" ("path");
|
||||
CREATE INDEX IF NOT EXISTS "payload_migrations_created_at_idx" ON "payload_migrations" ("created_at");
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "relation_a_rels" ADD CONSTRAINT "relation_a_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "relation_a"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "relation_a_rels" ADD CONSTRAINT "relation_a_rels_relation_b_fk" FOREIGN KEY ("relation_b_id") REFERENCES "relation_b"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "relation_b_rels" ADD CONSTRAINT "relation_b_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "relation_b"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "relation_b_rels" ADD CONSTRAINT "relation_b_rels_relation_a_fk" FOREIGN KEY ("relation_a_id") REFERENCES "relation_a"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "customs_customSelect" ADD CONSTRAINT "customs_customSelect_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "customArrays" ADD CONSTRAINT "customArrays_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "customArrays_locales" ADD CONSTRAINT "customArrays_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customArrays"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "customBlocks" ADD CONSTRAINT "customBlocks_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "customBlocks_locales" ADD CONSTRAINT "customBlocks_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customBlocks"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "customs_locales" ADD CONSTRAINT "customs_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "customs_rels" ADD CONSTRAINT "customs_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "customs_rels" ADD CONSTRAINT "customs_rels_relation_a_fk" FOREIGN KEY ("relation_a_id") REFERENCES "relation_a"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customs_v_version_customSelect" ADD CONSTRAINT "_customs_v_version_customSelect_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customArrays_v" ADD CONSTRAINT "_customArrays_v_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customArrays_v_locales" ADD CONSTRAINT "_customArrays_v_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customArrays_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customBlocks_v" ADD CONSTRAINT "_customBlocks_v_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customBlocks_v_locales" ADD CONSTRAINT "_customBlocks_v_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customBlocks_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customs_v_locales" ADD CONSTRAINT "_customs_v_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customs_v_rels" ADD CONSTRAINT "_customs_v_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customs_v_rels" ADD CONSTRAINT "_customs_v_rels_custom_schema_fk" FOREIGN KEY ("customs_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "_customs_v_rels" ADD CONSTRAINT "_customs_v_rels_relation_a_fk" FOREIGN KEY ("relation_a_id") REFERENCES "relation_a"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "payload_preferences"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down({ payload }: MigrateDownArgs): Promise<void> {
|
||||
await payload.db.drizzle.execute(sql`
|
||||
|
||||
DROP TABLE "posts";
|
||||
DROP TABLE "relation_a";
|
||||
DROP TABLE "relation_a_rels";
|
||||
DROP TABLE "relation_b";
|
||||
DROP TABLE "relation_b_rels";
|
||||
DROP TABLE "customs_customSelect";
|
||||
DROP TABLE "customArrays";
|
||||
DROP TABLE "customArrays_locales";
|
||||
DROP TABLE "customBlocks";
|
||||
DROP TABLE "customBlocks_locales";
|
||||
DROP TABLE "customs";
|
||||
DROP TABLE "customs_locales";
|
||||
DROP TABLE "customs_rels";
|
||||
DROP TABLE "_customs_v_version_customSelect";
|
||||
DROP TABLE "_customArrays_v";
|
||||
DROP TABLE "_customArrays_v_locales";
|
||||
DROP TABLE "_customBlocks_v";
|
||||
DROP TABLE "_customBlocks_v_locales";
|
||||
DROP TABLE "_customs_v";
|
||||
DROP TABLE "_customs_v_locales";
|
||||
DROP TABLE "_customs_v_rels";
|
||||
DROP TABLE "users";
|
||||
DROP TABLE "payload_preferences";
|
||||
DROP TABLE "payload_preferences_rels";
|
||||
DROP TABLE "payload_migrations";
|
||||
DROP TABLE "customGlobal";
|
||||
DROP TABLE "_customGlobal_v";`)
|
||||
}
|
||||
@@ -227,6 +227,86 @@ const GroupFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'localizedGroupArr',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'localizedGroupSelect',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['one', 'two'],
|
||||
name: 'select',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'localizedGroupRel',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'relationship',
|
||||
relationTo: 'text-fields',
|
||||
name: 'rel',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'localizedGroupManyRel',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'relationship',
|
||||
relationTo: 'text-fields',
|
||||
name: 'email',
|
||||
hasMany: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'localizedGroupPolyRel',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'relationship',
|
||||
relationTo: ['text-fields'],
|
||||
name: 'email',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'localizedGroupPolyHasManyRel',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'relationship',
|
||||
relationTo: ['text-fields'],
|
||||
name: 'email',
|
||||
hasMany: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,88 @@ const SelectFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'selectHasMany',
|
||||
hasMany: true,
|
||||
type: 'select',
|
||||
admin: {
|
||||
isClearable: true,
|
||||
isSortable: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: 'Value One',
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'Value Two',
|
||||
value: 'two',
|
||||
},
|
||||
{
|
||||
label: 'Value Three',
|
||||
value: 'three',
|
||||
},
|
||||
{
|
||||
label: 'Value Four',
|
||||
value: 'four',
|
||||
},
|
||||
{
|
||||
label: 'Value Five',
|
||||
value: 'five',
|
||||
},
|
||||
{
|
||||
label: 'Value Six',
|
||||
value: 'six',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'selectHasMany',
|
||||
hasMany: true,
|
||||
type: 'select',
|
||||
admin: {
|
||||
isClearable: true,
|
||||
isSortable: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: 'Value One',
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'Value Two',
|
||||
value: 'two',
|
||||
},
|
||||
{
|
||||
label: 'Value Three',
|
||||
value: 'three',
|
||||
},
|
||||
{
|
||||
label: 'Value Four',
|
||||
value: 'four',
|
||||
},
|
||||
{
|
||||
label: 'Value Five',
|
||||
value: 'five',
|
||||
},
|
||||
{
|
||||
label: 'Value Six',
|
||||
value: 'six',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'selectHasManyLocalized',
|
||||
type: 'select',
|
||||
|
||||
@@ -767,6 +767,39 @@ describe('fields', () => {
|
||||
await expect(page.locator('.Toastify')).toContainText('Please correct invalid fields')
|
||||
})
|
||||
|
||||
test('should duplicate block', async () => {
|
||||
await page.goto(url.create)
|
||||
const firstRow = page.locator('#field-blocks #blocks-row-0')
|
||||
const rowActions = firstRow.locator('.collapsible__actions')
|
||||
await expect(rowActions).toBeVisible()
|
||||
|
||||
await rowActions.locator('.array-actions__button').click()
|
||||
const duplicateButton = rowActions.locator('.array-actions__action.array-actions__duplicate')
|
||||
await expect(duplicateButton).toBeVisible()
|
||||
await duplicateButton.click()
|
||||
|
||||
const blocks = page.locator('#field-blocks > .blocks-field__rows > div')
|
||||
expect(await blocks.count()).toEqual(4)
|
||||
})
|
||||
|
||||
test('should save when duplicating subblocks', async () => {
|
||||
await page.goto(url.create)
|
||||
const subblocksRow = page.locator('#field-blocks #blocks-row-2')
|
||||
const rowActions = subblocksRow.locator('.collapsible__actions').first()
|
||||
await expect(rowActions).toBeVisible()
|
||||
|
||||
await rowActions.locator('.array-actions__button').click()
|
||||
const duplicateButton = rowActions.locator('.array-actions__action.array-actions__duplicate')
|
||||
await expect(duplicateButton).toBeVisible()
|
||||
await duplicateButton.click()
|
||||
|
||||
const blocks = page.locator('#field-blocks > .blocks-field__rows > div')
|
||||
expect(await blocks.count()).toEqual(4)
|
||||
|
||||
await page.click('#action-save')
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
})
|
||||
|
||||
describe('row manipulation', () => {
|
||||
describe('react hooks', () => {
|
||||
test('should add 2 new block rows', async () => {
|
||||
|
||||
@@ -573,6 +573,54 @@ describe('Fields', () => {
|
||||
|
||||
expect(resInSecond.totalDocs).toBe(1)
|
||||
})
|
||||
|
||||
it('should CRUD within array hasMany', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: 'select-fields',
|
||||
data: { array: [{ selectHasMany: ['one', 'two'] }] },
|
||||
})
|
||||
|
||||
expect(doc.array[0].selectHasMany).toStrictEqual(['one', 'two'])
|
||||
|
||||
const upd = await payload.update({
|
||||
collection: 'select-fields',
|
||||
id: doc.id,
|
||||
data: {
|
||||
array: [
|
||||
{
|
||||
id: doc.array[0].id,
|
||||
selectHasMany: ['six'],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(upd.array[0].selectHasMany).toStrictEqual(['six'])
|
||||
})
|
||||
|
||||
it('should CRUD within array + group hasMany', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: 'select-fields',
|
||||
data: { array: [{ group: { selectHasMany: ['one', 'two'] } }] },
|
||||
})
|
||||
|
||||
expect(doc.array[0].group.selectHasMany).toStrictEqual(['one', 'two'])
|
||||
|
||||
const upd = await payload.update({
|
||||
collection: 'select-fields',
|
||||
id: doc.id,
|
||||
data: {
|
||||
array: [
|
||||
{
|
||||
id: doc.array[0].id,
|
||||
group: { selectHasMany: ['six'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(upd.array[0].group.selectHasMany).toStrictEqual(['six'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('number', () => {
|
||||
@@ -842,6 +890,37 @@ describe('Fields', () => {
|
||||
|
||||
expect(resInSecond.totalDocs).toBe(1)
|
||||
})
|
||||
|
||||
it('should properly query numbers with exists operator', async () => {
|
||||
await payload.create({
|
||||
collection: 'number-fields',
|
||||
data: {
|
||||
number: null,
|
||||
},
|
||||
})
|
||||
|
||||
const numbersExist = await payload.find({
|
||||
collection: 'number-fields',
|
||||
where: {
|
||||
number: {
|
||||
exists: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(numbersExist.totalDocs).toBe(4)
|
||||
|
||||
const numbersNotExists = await payload.find({
|
||||
collection: 'number-fields',
|
||||
where: {
|
||||
number: {
|
||||
exists: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(numbersNotExists.docs).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
if (isMongoose(payload)) {
|
||||
@@ -1299,6 +1378,324 @@ describe('Fields', () => {
|
||||
expect(res.camelCaseGroup.array[0].array[0].text).toBe('nested')
|
||||
expect(res.camelCaseGroup.nesGroup.arr[0].text).toBe('nestedCamel')
|
||||
})
|
||||
|
||||
it('should insert/update/read localized group with array inside', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: 'group-fields',
|
||||
locale: 'en',
|
||||
data: {
|
||||
group: { text: 'req' },
|
||||
localizedGroupArr: {
|
||||
array: [{ text: 'text-en' }],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(doc.localizedGroupArr.array[0].text).toBe('text-en')
|
||||
|
||||
const esDoc = await payload.update({
|
||||
collection: 'group-fields',
|
||||
locale: 'es',
|
||||
id: doc.id,
|
||||
data: {
|
||||
localizedGroupArr: {
|
||||
array: [{ text: 'text-es' }],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(esDoc.localizedGroupArr.array[0].text).toBe('text-es')
|
||||
|
||||
const allDoc = await payload.findByID({
|
||||
collection: 'group-fields',
|
||||
id: doc.id,
|
||||
locale: 'all',
|
||||
})
|
||||
|
||||
expect(allDoc.localizedGroupArr.en.array[0].text).toBe('text-en')
|
||||
expect(allDoc.localizedGroupArr.es.array[0].text).toBe('text-es')
|
||||
})
|
||||
|
||||
it('should insert/update/read localized group with select hasMany inside', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: 'group-fields',
|
||||
locale: 'en',
|
||||
data: {
|
||||
group: { text: 'req' },
|
||||
localizedGroupSelect: {
|
||||
select: ['one', 'two'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(doc.localizedGroupSelect.select).toStrictEqual(['one', 'two'])
|
||||
|
||||
const esDoc = await payload.update({
|
||||
collection: 'group-fields',
|
||||
locale: 'es',
|
||||
id: doc.id,
|
||||
data: {
|
||||
localizedGroupSelect: {
|
||||
select: ['one'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(esDoc.localizedGroupSelect.select).toStrictEqual(['one'])
|
||||
|
||||
const allDoc = await payload.findByID({
|
||||
collection: 'group-fields',
|
||||
id: doc.id,
|
||||
locale: 'all',
|
||||
})
|
||||
|
||||
expect(allDoc.localizedGroupSelect.en.select).toStrictEqual(['one', 'two'])
|
||||
expect(allDoc.localizedGroupSelect.es.select).toStrictEqual(['one'])
|
||||
})
|
||||
|
||||
it('should insert/update/read localized group with relationship inside', async () => {
|
||||
const rel_1 = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text: 'pro123@gmail.com' },
|
||||
})
|
||||
|
||||
const rel_2 = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text: 'frank@gmail.com' },
|
||||
})
|
||||
|
||||
const doc = await payload.create({
|
||||
collection: 'group-fields',
|
||||
depth: 0,
|
||||
data: {
|
||||
group: { text: 'requireddd' },
|
||||
localizedGroupRel: {
|
||||
rel: rel_1.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(doc.localizedGroupRel.rel).toBe(rel_1.id)
|
||||
|
||||
const upd = await payload.update({
|
||||
collection: 'group-fields',
|
||||
depth: 0,
|
||||
id: doc.id,
|
||||
locale: 'es',
|
||||
data: {
|
||||
localizedGroupRel: {
|
||||
rel: rel_2.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(upd.localizedGroupRel.rel).toBe(rel_2.id)
|
||||
|
||||
const docAll = await payload.findByID({
|
||||
collection: 'group-fields',
|
||||
id: doc.id,
|
||||
locale: 'all',
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
expect(docAll.localizedGroupRel.en.rel).toBe(rel_1.id)
|
||||
expect(docAll.localizedGroupRel.es.rel).toBe(rel_2.id)
|
||||
})
|
||||
|
||||
it('should insert/update/read localized group with hasMany relationship inside', async () => {
|
||||
const rel_1 = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text: 'pro123@gmail.com' },
|
||||
})
|
||||
|
||||
const rel_2 = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text: 'frank@gmail.com' },
|
||||
})
|
||||
|
||||
const doc = await payload.create({
|
||||
collection: 'group-fields',
|
||||
depth: 0,
|
||||
data: {
|
||||
group: { text: 'requireddd' },
|
||||
localizedGroupManyRel: {
|
||||
email: [rel_1.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(doc.localizedGroupManyRel.email).toStrictEqual([rel_1.id])
|
||||
|
||||
const upd = await payload.update({
|
||||
collection: 'group-fields',
|
||||
depth: 0,
|
||||
id: doc.id,
|
||||
locale: 'es',
|
||||
data: {
|
||||
localizedGroupManyRel: {
|
||||
email: [rel_2.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(upd.localizedGroupManyRel.email).toStrictEqual([rel_2.id])
|
||||
|
||||
const docAll = await payload.findByID({
|
||||
collection: 'group-fields',
|
||||
id: doc.id,
|
||||
locale: 'all',
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
expect(docAll.localizedGroupManyRel.en.email).toStrictEqual([rel_1.id])
|
||||
expect(docAll.localizedGroupManyRel.es.email).toStrictEqual([rel_2.id])
|
||||
})
|
||||
|
||||
it('should insert/update/read localized group with poly relationship inside', async () => {
|
||||
const rel_1 = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text: 'pro123@gmail.com' },
|
||||
})
|
||||
|
||||
const rel_2 = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text: 'frank@gmail.com' },
|
||||
})
|
||||
|
||||
const doc = await payload.create({
|
||||
collection: 'group-fields',
|
||||
depth: 0,
|
||||
data: {
|
||||
group: { text: 'requireddd' },
|
||||
localizedGroupPolyRel: {
|
||||
email: {
|
||||
relationTo: 'text-fields',
|
||||
value: rel_1.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(doc.localizedGroupPolyRel.email).toStrictEqual({
|
||||
relationTo: 'text-fields',
|
||||
value: rel_1.id,
|
||||
})
|
||||
|
||||
const upd = await payload.update({
|
||||
collection: 'group-fields',
|
||||
depth: 0,
|
||||
id: doc.id,
|
||||
locale: 'es',
|
||||
data: {
|
||||
localizedGroupPolyRel: {
|
||||
email: {
|
||||
value: rel_2.id,
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(upd.localizedGroupPolyRel.email).toStrictEqual({
|
||||
value: rel_2.id,
|
||||
relationTo: 'text-fields',
|
||||
})
|
||||
|
||||
const docAll = await payload.findByID({
|
||||
collection: 'group-fields',
|
||||
id: doc.id,
|
||||
locale: 'all',
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
expect(docAll.localizedGroupPolyRel.en.email).toStrictEqual({
|
||||
value: rel_1.id,
|
||||
relationTo: 'text-fields',
|
||||
})
|
||||
expect(docAll.localizedGroupPolyRel.es.email).toStrictEqual({
|
||||
value: rel_2.id,
|
||||
relationTo: 'text-fields',
|
||||
})
|
||||
})
|
||||
|
||||
it('should insert/update/read localized group with poly hasMany relationship inside', async () => {
|
||||
const rel_1 = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text: 'pro123@gmail.com' },
|
||||
})
|
||||
|
||||
const rel_2 = await payload.create({
|
||||
collection: 'text-fields',
|
||||
data: { text: 'frank@gmail.com' },
|
||||
})
|
||||
|
||||
const doc = await payload.create({
|
||||
collection: 'group-fields',
|
||||
depth: 0,
|
||||
data: {
|
||||
group: { text: 'requireddd' },
|
||||
localizedGroupPolyHasManyRel: {
|
||||
email: [
|
||||
{
|
||||
relationTo: 'text-fields',
|
||||
value: rel_1.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(doc.localizedGroupPolyHasManyRel.email).toStrictEqual([
|
||||
{
|
||||
relationTo: 'text-fields',
|
||||
value: rel_1.id,
|
||||
},
|
||||
])
|
||||
|
||||
const upd = await payload.update({
|
||||
collection: 'group-fields',
|
||||
depth: 0,
|
||||
id: doc.id,
|
||||
locale: 'es',
|
||||
data: {
|
||||
localizedGroupPolyHasManyRel: {
|
||||
email: [
|
||||
{
|
||||
value: rel_2.id,
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(upd.localizedGroupPolyHasManyRel.email).toStrictEqual([
|
||||
{
|
||||
value: rel_2.id,
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
])
|
||||
|
||||
const docAll = await payload.findByID({
|
||||
collection: 'group-fields',
|
||||
id: doc.id,
|
||||
locale: 'all',
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
expect(docAll.localizedGroupPolyHasManyRel.en.email).toStrictEqual([
|
||||
{
|
||||
value: rel_1.id,
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
])
|
||||
expect(docAll.localizedGroupPolyHasManyRel.es.email).toStrictEqual([
|
||||
{
|
||||
value: rel_2.id,
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('tabs', () => {
|
||||
|
||||
@@ -271,6 +271,17 @@ export interface ArrayField {
|
||||
id?: string | null
|
||||
}[]
|
||||
| null
|
||||
nestedArrayLocalized?:
|
||||
| {
|
||||
array?:
|
||||
| {
|
||||
text?: string | null
|
||||
id?: string | null
|
||||
}[]
|
||||
| null
|
||||
id?: string | null
|
||||
}[]
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
@@ -841,6 +852,37 @@ export interface GroupField {
|
||||
| null
|
||||
}
|
||||
}
|
||||
localizedGroupArr?: {
|
||||
array?:
|
||||
| {
|
||||
text?: string | null
|
||||
id?: string | null
|
||||
}[]
|
||||
| null
|
||||
}
|
||||
localizedGroupSelect?: {
|
||||
select?: ('one' | 'two')[] | null
|
||||
}
|
||||
localizedGroupRel?: {
|
||||
rel?: (string | null) | TextField
|
||||
}
|
||||
localizedGroupManyRel?: {
|
||||
email?: (string | TextField)[] | null
|
||||
}
|
||||
localizedGroupPolyRel?: {
|
||||
email?: {
|
||||
relationTo: 'text-fields'
|
||||
value: string | TextField
|
||||
} | null
|
||||
}
|
||||
localizedGroupPolyHasManyRel?: {
|
||||
email?:
|
||||
| {
|
||||
relationTo: 'text-fields'
|
||||
value: string | TextField
|
||||
}[]
|
||||
| null
|
||||
}
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
@@ -1116,6 +1158,15 @@ export interface SelectField {
|
||||
select?: ('one' | 'two' | 'three') | null
|
||||
selectReadOnly?: ('one' | 'two' | 'three') | null
|
||||
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null
|
||||
array?:
|
||||
| {
|
||||
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null
|
||||
group?: {
|
||||
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null
|
||||
}
|
||||
id?: string | null
|
||||
}[]
|
||||
| null
|
||||
selectHasManyLocalized?: ('one' | 'two')[] | null
|
||||
selectI18n?: ('one' | 'two' | 'three') | null
|
||||
simple?: ('One' | 'Two' | 'Three') | null
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import fs from 'fs'
|
||||
|
||||
const removeFiles = (dir) => {
|
||||
const removeFiles = (dir, nameFilter?: (name: string) => boolean) => {
|
||||
if (!fs.existsSync(dir)) return
|
||||
|
||||
fs.readdirSync(dir).forEach((f) => {
|
||||
if (nameFilter && !nameFilter(f)) return
|
||||
return fs.rmSync(`${dir}/${f}`, { recursive: true })
|
||||
})
|
||||
}
|
||||
|
||||
37
test/localization/collections/Blocks/index.ts
Normal file
37
test/localization/collections/Blocks/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const blocksCollectionSlug = 'blocks-fields'
|
||||
|
||||
export const BlocksCollection: CollectionConfig = {
|
||||
slug: blocksCollectionSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'content',
|
||||
label: 'Content',
|
||||
type: 'blocks',
|
||||
localized: true,
|
||||
blocks: [
|
||||
{
|
||||
slug: 'blockInsideBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'content',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'textBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
81
test/localization/collections/Group/index.ts
Normal file
81
test/localization/collections/Group/index.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const groupSlug = 'groups'
|
||||
|
||||
export const Group: CollectionConfig = {
|
||||
slug: groupSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'groupLocalizedRow',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'groupLocalized',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
localized: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'deep',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'first',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
42
test/localization/collections/NestedArray/index.ts
Normal file
42
test/localization/collections/NestedArray/index.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const NestedArray: CollectionConfig = {
|
||||
slug: 'nested-arrays',
|
||||
fields: [
|
||||
{
|
||||
name: 'arrayWithBlocks',
|
||||
type: 'array',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'blocksWithinArray',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'someBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'relationWithinBlock',
|
||||
type: 'relationship',
|
||||
relationTo: 'localized-posts',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'arrayWithLocalizedRelation',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'localizedRelation',
|
||||
type: 'relationship',
|
||||
localized: true,
|
||||
relationTo: 'localized-posts',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
86
test/localization/collections/NestedFields/index.ts
Normal file
86
test/localization/collections/NestedFields/index.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const NestedFields: CollectionConfig = {
|
||||
slug: 'nested-field-tables',
|
||||
fields: [
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'relation',
|
||||
type: 'relationship',
|
||||
relationTo: ['localized-posts'],
|
||||
},
|
||||
{
|
||||
name: 'hasManyRelation',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
relationTo: 'localized-posts',
|
||||
},
|
||||
{
|
||||
name: 'hasManyPolyRelation',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
relationTo: ['localized-posts'],
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['one', 'two', 'three'],
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
type: 'number',
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
hasMany: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
localized: true,
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedBlocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'content',
|
||||
fields: [
|
||||
{
|
||||
name: 'relation',
|
||||
type: 'relationship',
|
||||
relationTo: ['localized-posts'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'relation',
|
||||
type: 'relationship',
|
||||
relationTo: ['localized-posts'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
66
test/localization/collections/Tab/index.ts
Normal file
66
test/localization/collections/Tab/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const tabSlug = 'tabs'
|
||||
|
||||
export const Tab: CollectionConfig = {
|
||||
slug: tabSlug,
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
name: 'tabLocalized',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tab',
|
||||
fields: [
|
||||
{
|
||||
localized: true,
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'deep',
|
||||
fields: [
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
localized: true,
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'first',
|
||||
fields: [
|
||||
{
|
||||
localized: true,
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -4,8 +4,13 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
|
||||
import { devUser } from '../credentials'
|
||||
import { englishLocale } from '../globals/config'
|
||||
import { ArrayCollection } from './collections/Array'
|
||||
import { BlocksCollection } from './collections/Blocks'
|
||||
import { Group } from './collections/Group'
|
||||
import { NestedArray } from './collections/NestedArray'
|
||||
import { NestedFields } from './collections/NestedFields'
|
||||
import { NestedToArrayAndBlock } from './collections/NestedToArrayAndBlock'
|
||||
import { RestrictedByLocaleCollection } from './collections/RestrictedByLocale'
|
||||
import { Tab } from './collections/Tab'
|
||||
import {
|
||||
blocksWithLocalizedSameName,
|
||||
defaultLocale,
|
||||
@@ -41,6 +46,9 @@ const openAccess = {
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
BlocksCollection,
|
||||
NestedArray,
|
||||
NestedFields,
|
||||
{
|
||||
auth: true,
|
||||
fields: [
|
||||
@@ -127,6 +135,16 @@ export default buildConfigWithDefaults({
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'nestedArray',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
slug: 'text',
|
||||
},
|
||||
@@ -144,6 +162,41 @@ export default buildConfigWithDefaults({
|
||||
required: true,
|
||||
type: 'blocks',
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
name: 'myTab',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedArray2',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'nestedText',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
slug: withRequiredLocalizedFields,
|
||||
},
|
||||
@@ -235,6 +288,8 @@ export default buildConfigWithDefaults({
|
||||
slug: 'dummy',
|
||||
},
|
||||
NestedToArrayAndBlock,
|
||||
Group,
|
||||
Tab,
|
||||
{
|
||||
slug: localizedSortSlug,
|
||||
access: openAccess,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user