Compare commits

..

1 Commits

Author SHA1 Message Date
Dan Ribbens
8a87f6e65a fix(db-postgres): schemas and other drizzle fixes 2024-07-31 17:21:34 -04:00
167 changed files with 9686 additions and 65163 deletions

View File

@@ -2,69 +2,50 @@ name: Bug Report v3
description: Create a bug report for Payload v3 (beta)
labels: ['status: needs-triage', 'v3']
body:
- 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.
validations:
required: false
- type: input
id: version
attributes:
label: Payload Version
description: What version of Payload are you running?
validations:
required: true
- type: input
id: node-version
attributes:
label: Node Version
description: What version of Node are you running?
validations:
required: true
- type: input
id: nextjs-version
attributes:
label: Next.js Version
description: What version of Next.js are you running?
validations:
required: true
- type: textarea
attributes:
label: Describe the Bug
validations:
required: true
- type: input
id: reproduction-link
attributes:
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` or follow the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md) for more information.
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: dropdown
- type: input
id: adapters-plugins
attributes:
label: Which area(s) are affected? (Select all that apply)
multiple: true
options:
- 'Not sure'
- 'area: core'
- '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:
label: Environment Info
description: Paste output from `pnpm payload info` (>= beta.92) _or_ Payload, Node.js, and Next.js versions.
render: bash
placeholder: |
Payload:
Node.js:
Next.js:
validations:
required: true
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.

View File

@@ -1,10 +1,23 @@
<!--
## Description
For external contributors, please include:
<!-- Please include a summary of the pull request and any related issues it fixes. Please also include relevant motivation and context. -->
- 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.
- [ ] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository.
Ensure you have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository.
## Type of change
-->
<!-- Please delete options that are not relevant. -->
- [ ] Chore (non-breaking change which does not add functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Change to the [templates](https://github.com/payloadcms/payload/tree/main/templates) directory (does not affect core functionality)
- [ ] Change to the [examples](https://github.com/payloadcms/payload/tree/main/examples) directory (does not affect core functionality)
- [ ] This change requires a documentation update
## Checklist:
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation

View File

@@ -1,13 +0,0 @@
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'],
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,74 +0,0 @@
# Release Commenter
This GitHub Action automatically comments on and/or labels Issues and PRs when a fix is released for them.
> [!IMPORTANT]
> 🔧 Heavily modified version of https://github.com/apexskier/github-release-commenter
## Fork Modifications
- Filters to closed PRs only
- Adds tag filter to support non-linear releases
- Better logging
- Moved to pnpm
- Uses @vercel/ncc for packaging
- Comments on locked issues by unlocking then re-locking
## How it works
Use this action in a workflow [triggered by a release](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#release). It will scan commits between that and the prior release, find associated Issues and PRs, and comment on them to let people know a release has been made. Associated Issues and PRs can be directly [linked](https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) to the commit or manually linked from a PR associated with the commit.
## Inputs
**GITHUB_TOKEN**
A GitHub personal access token with repo scope, such as [`secrets.GITHUB_TOKEN`](https://docs.github.com/en/free-pro-team@latest/actions/reference/authentication-in-a-workflow#about-the-github_token-secret).
**comment-template** (optional)
Override the comment posted on Issues and PRs. Set to the empty string to disable commenting. Several variables strings will be automatically replaced:
- `{release_link}` - a markdown link to the release
- `{release_name}` - the release's name
- `{release_tag}` - the release's tag
**label-template** (optional)
Add the given label. Multiple labels can be separated by commas. Several variable strings will be automatically replaced:
- `{release_name}` - the release's name
- `{release_tag}` - the release's tag
**skip-label** (optional)
Skip processing if any of the given labels are present. Same processing rules as **label-template**. Default is "dependencies".
## Example
```yml
on:
release:
types: [published]
jobs:
release:
steps:
- uses: apexskier/github-release-commenter@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
comment-template: |
Release {release_link} addresses this.
```
## Known limitations
These are some known limitations of this action. I'd like to try to address them in the future.
- Non-linear releases aren't supported. For example, releasing a patch to a prior major release after a new major release has been bumped.
- Non-sequential releases aren't supported. For example, if you release multiple prereleases between two official releases, this will only create a comment for the first prerelease in which a fix is released, not the final release.
- The first release for a project will be ignored. This is intentional, as the use case is unlikely. Most projects will either have several alphas that don't need release comments, or won't use issues/PRs for the first commit.
- If a large number of things are commented on, you may see the error `Error: You have triggered an abuse detection mechanism. Please wait a few minutes before you try again.`. Consider using the `skip-label` input to reduce your load on the GitHub API.
## Versions
Workflows will automatically update the tags `v1` and `latest`, allowing you to reference one of those instead of locking to a specific release.

View File

@@ -1,32 +0,0 @@
name: Release Commenter
description: Comment on PRs and Issues when a fix is released
branding:
icon: message-square
color: blue
inputs:
GITHUB_TOKEN:
description: |
A GitHub personal access token with repo scope, such as
secrets.GITHUB_TOKEN.
required: true
comment-template:
description: |
Text template for the comment string.
required: false
default: |
Included in release {release_link}
label-template:
description: Add the given label. Multiple labels can be separated by commas.
required: false
skip-label:
description: Skip commenting if any of the given label are present. Multiple labels can be separated by commas.
required: false
default: "dependencies"
tag-filter:
description: |
Filter tags by a regular expression. Must be escaped. e.g. 'v\\d' to isolate tags between major versions.
required: false
default: null
runs:
using: node20
main: dist/index.js

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +0,0 @@
module.exports = {
testEnvironment: 'node',
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/dist/'],
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest'],
},
}

View File

@@ -1,34 +0,0 @@
{
"name": "release-commenter",
"version": "0.0.0",
"description": "GitHub Action to automatically comment on PRs and Issues when a fix is released.",
"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"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,266 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`tests feature tests can apply labels 1`] = `
[
[
{
"issue_number": 123,
"labels": [
":dart: landed",
"release-current_tag_name",
"Release Name",
],
},
],
[
{
"issue_number": 7,
"labels": [
":dart: landed",
"release-current_tag_name",
"Release Name",
],
},
],
]
`;
exports[`tests main test 1`] = `
{
"graphql": [MockFunction] {
"calls": [
[
"
{
resource(url: "http://repository/commit/SHA1") {
... on Commit {
messageHeadlineHTML
messageBodyHTML
associatedPullRequests(first: 10) {
pageInfo {
hasNextPage
}
edges {
node {
bodyHTML
number
state
labels(first: 10) {
pageInfo {
hasNextPage
}
nodes {
name
}
}
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
pageInfo {
hasNextPage
}
nodes {
... on ConnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
... on DisconnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
}
}
}
}
}
}
}
}
",
],
[
"
{
resource(url: "http://repository/commit/SHA2") {
... on Commit {
messageHeadlineHTML
messageBodyHTML
associatedPullRequests(first: 10) {
pageInfo {
hasNextPage
}
edges {
node {
bodyHTML
number
state
labels(first: 10) {
pageInfo {
hasNextPage
}
nodes {
name
}
}
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
pageInfo {
hasNextPage
}
nodes {
... on ConnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
... on DisconnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
}
}
}
}
}
}
}
}
",
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
],
},
"rest": {
"issues": {
"addLabels": [MockFunction],
"createComment": [MockFunction] {
"calls": [
[
{
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
"issue_number": 3,
},
],
[
{
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
"issue_number": 123,
},
],
[
{
"body": "Included in release [current_tag_name](http://current_release). Replacements: current_tag_name, current_tag_name.",
"issue_number": 7,
},
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
],
},
"get": [MockFunction] {
"calls": [
[
{
"issue_number": 3,
},
],
[
{
"issue_number": 123,
},
],
[
{
"issue_number": 7,
},
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
{
"type": "return",
"value": Promise {},
},
],
},
},
"repos": {
"compareCommits": [MockFunction] {
"calls": [
[
{
"base": "prior_tag_name",
"head": "current_tag_name",
},
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
],
},
"listReleases": [MockFunction] {
"calls": [
[
{
"per_page": 100,
},
],
],
"results": [
{
"type": "return",
"value": Promise {},
},
],
},
},
},
}
`;

View File

@@ -1,399 +0,0 @@
import type * as githubModule from '@actions/github'
import type * as coreModule from '@actions/core'
import { mock } from 'node:test'
jest.mock('@actions/core')
jest.mock('@actions/github')
type Mocked<T> = {
-readonly [P in keyof T]: T[P] extends Function ? jest.Mock<T[P]> : jest.Mocked<Partial<T[P]>>
}
const github = require('@actions/github') as jest.Mocked<Mocked<typeof githubModule>>
const core = require('@actions/core') as jest.Mocked<Mocked<typeof coreModule>>
describe('tests', () => {
let mockOctokit: any = {}
let currentTag: string = 'current_tag_name'
;(core.warning as any) = jest.fn(console.warn.bind(console))
;(core.error as any) = jest.fn(console.error.bind(console))
let commentTempate: string = ''
let labelTemplate: string | null = null
const skipLabelTemplate: string | null = 'skip,test'
let tagFilter: string | RegExp | null = null
let simpleMockOctokit: any = {}
beforeEach(() => {
tagFilter = null
currentTag = 'current_tag_name'
;(github.context as any) = {
payload: {
repo: {
owner: 'owner',
repo: 'repo',
},
release: {
tag_name: currentTag,
},
repository: { html_url: 'http://repository' },
},
}
github.getOctokit.mockReset().mockImplementationOnce(((token: string) => {
expect(token).toBe('GITHUB_TOKEN_VALUE')
return mockOctokit
}) as any)
;(core.getInput as any).mockImplementation((key: string) => {
if (key == 'GITHUB_TOKEN') {
return 'GITHUB_TOKEN_VALUE'
}
if (key == 'comment-template') {
return commentTempate
}
if (key == 'label-template') {
return labelTemplate
}
if (key == 'skip-label') {
return skipLabelTemplate
}
if (key == 'tag-filter') {
return tagFilter
}
fail(`Unexpected input key ${key}`)
})
commentTempate =
'Included in release {release_link}. Replacements: {release_name}, {release_tag}.'
labelTemplate = null
simpleMockOctokit = {
rest: {
issues: {
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
createComment: jest.fn(() => Promise.resolve()),
addLabels: jest.fn(() => Promise.resolve()),
},
repos: {
listReleases: jest.fn(() =>
Promise.resolve({
data: [
{
name: 'Release Name',
tag_name: 'current_tag_name',
html_url: 'http://current_release',
},
{
tag_name: 'prior_tag_name',
html_url: 'http://prior_release',
},
],
}),
),
compareCommits: jest.fn(() =>
Promise.resolve({
data: { commits: [{ sha: 'SHA1' }] },
}),
),
},
},
graphql: jest.fn(() =>
Promise.resolve({
resource: {
messageHeadlineHTML: '',
messageBodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
associatedPullRequests: {
pageInfo: { hasNextPage: false },
edges: [],
},
},
}),
),
}
})
afterEach(() => {
expect(core.error).not.toHaveBeenCalled()
expect(core.warning).not.toHaveBeenCalled()
expect(core.setFailed).not.toHaveBeenCalled()
})
test('main test', async () => {
mockOctokit = {
...simpleMockOctokit,
rest: {
issues: {
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
createComment: jest.fn(() => Promise.resolve()),
addLabels: jest.fn(() => Promise.resolve()),
},
repos: {
listReleases: jest.fn(() =>
Promise.resolve({
data: [
{
tag_name: 'current_tag_name',
html_url: 'http://current_release',
},
{
tag_name: 'prior_tag_name',
html_url: 'http://prior_release',
},
],
}),
),
compareCommits: jest.fn(() =>
Promise.resolve({
data: { commits: [{ sha: 'SHA1' }, { sha: 'SHA2' }] },
}),
),
},
},
graphql: jest.fn(() =>
Promise.resolve({
resource: {
messageHeadlineHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #3.">Closes</span> <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="718013420" data-permission-text="Title is private" data-url="https://github.com/apexskier/github-release-commenter/issues/1" data-hovercard-type="issue" data-hovercard-url="/apexskier/github-release-commenter/issues/1/hovercard" href="https://github.com/apexskier/github-release-commenter/issues/1">#1</a>',
messageBodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
associatedPullRequests: {
pageInfo: { hasNextPage: false },
edges: [
{
node: {
bodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #4.">Closes</span> <span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #5.">Closes</span>',
number: 9,
labels: {
pageInfo: { hasNextPage: false },
nodes: [{ name: 'label1' }, { name: 'label2' }],
},
timelineItems: {
pageInfo: { hasNextPage: false },
nodes: [
{
isCrossRepository: true,
__typename: 'ConnectedEvent',
subject: { number: 1 },
},
{
isCrossRepository: false,
__typename: 'ConnectedEvent',
subject: { number: 2 },
},
{
isCrossRepository: false,
__typename: 'DisconnectedEvent',
subject: { number: 2 },
},
{
isCrossRepository: false,
__typename: 'ConnectedEvent',
subject: { number: 2 },
},
],
},
},
},
{
node: {
bodyHTML: '',
number: 42,
labels: {
pageInfo: { hasNextPage: false },
nodes: [{ name: 'label1' }, { name: 'skip' }],
},
timelineItems: {
pageInfo: { hasNextPage: false },
nodes: [
{
isCrossRepository: true,
__typename: 'ConnectedEvent',
subject: { number: 82 },
},
],
},
},
},
],
},
},
}),
),
}
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>(setImmediate)
expect(mockOctokit).toMatchSnapshot()
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledTimes(3)
})
describe('can filter tags', () => {
const v3prev = 'v3.0.1'
const v3current = 'v3.0.2'
const v2prev = 'v2.0.1'
const v2current = 'v2.0.2'
const listReleasesData = [
{
name: 'Current Release Name',
tag_name: v3current,
html_url: 'http://v3.0.2',
},
{
name: 'Prev Release Name',
tag_name: v3prev,
html_url: 'http://v3.0.1',
},
{
name: 'v2 Current Release Name',
tag_name: v2current,
html_url: 'http://v2.0.2',
},
{
name: 'v2 Prev Release Name',
tag_name: v2prev,
html_url: 'http://v2.0.1',
},
]
it.each`
description | prevTag | currentTag | filter
${'no filter'} | ${v3prev} | ${v3current} | ${null}
${'v3'} | ${v3prev} | ${v3current} | ${'v\\d'}
${'v2'} | ${v2prev} | ${v2current} | ${'v\\d'}
`('should filter tags with $description', async ({ prevTag, currentTag, filter }) => {
// @ts-ignore
github.context.payload.release.tag_name = currentTag
tagFilter = filter
mockOctokit = {
...simpleMockOctokit,
rest: {
issues: {
get: jest.fn(() => Promise.resolve({ data: { locked: false } })),
createComment: jest.fn(() => Promise.resolve()),
addLabels: jest.fn(() => Promise.resolve()),
},
repos: {
listReleases: jest.fn(() =>
Promise.resolve({
data: listReleasesData,
}),
),
compareCommits: jest.fn(() =>
Promise.resolve({
data: { commits: [{ sha: 'SHA1' }] },
}),
),
},
},
graphql: jest.fn(() =>
Promise.resolve({
resource: {
messageHeadlineHTML: '',
messageBodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
associatedPullRequests: {
pageInfo: { hasNextPage: false },
edges: [],
},
},
}),
),
}
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>(resolve => setImmediate(() => resolve()))
expect(github.getOctokit).toHaveBeenCalled()
expect(mockOctokit.rest.repos.compareCommits.mock.calls).toEqual([
[{ base: prevTag, head: currentTag }],
])
})
})
describe('feature tests', () => {
beforeEach(() => {
mockOctokit = simpleMockOctokit
})
it('can disable comments', async () => {
commentTempate = ''
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>(resolve => setImmediate(() => resolve()))
expect(github.getOctokit).toHaveBeenCalled()
expect(mockOctokit.rest.issues.createComment).not.toHaveBeenCalled()
})
it('should unlock and comment', async () => {
mockOctokit = {
...simpleMockOctokit,
rest: {
...simpleMockOctokit.rest,
issues: {
// Return locked for both issues to be commented on
get: jest.fn(() => Promise.resolve({ data: { locked: true } })),
lock: jest.fn(() => Promise.resolve()),
unlock: jest.fn(() => Promise.resolve()),
createComment: jest.fn(() => Promise.resolve()),
},
},
graphql: jest.fn(() =>
Promise.resolve({
resource: {
messageHeadlineHTML: '',
messageBodyHTML:
'<span class="issue-keyword tooltipped tooltipped-se" aria-label="This commit closes issue #123.">Closes</span> <p><span class="issue-keyword tooltipped tooltipped-se" aria-label="This pull request closes issue #7.">Closes</span>',
associatedPullRequests: {
pageInfo: { hasNextPage: false },
edges: [],
},
},
}),
),
}
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>(resolve => setImmediate(() => resolve()))
expect(github.getOctokit).toHaveBeenCalled()
// Should call once for both linked issues
expect(mockOctokit.rest.issues.unlock).toHaveBeenCalledTimes(2)
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledTimes(2)
expect(mockOctokit.rest.issues.lock).toHaveBeenCalledTimes(2)
})
it.skip('can apply labels', async () => {
labelTemplate = ':dart: landed,release-{release_tag},{release_name}'
jest.isolateModules(() => {
require('./index')
})
await new Promise<void>(resolve => setImmediate(() => resolve()))
expect(github.getOctokit).toHaveBeenCalled()
expect(mockOctokit.rest.issues.addLabels.mock.calls).toMatchSnapshot()
})
})
})

View File

@@ -1,349 +0,0 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import type * as Webhooks from '@octokit/webhooks-types'
const closesMatcher = /aria-label="This (?:commit|pull request) closes issue #(\d+)\."/g
const releaseLinkTemplateRegex = /{release_link}/g
const releaseNameTemplateRegex = /{release_name}/g
const releaseTagTemplateRegex = /{release_tag}/g
;(async function main() {
try {
const payload = github.context.payload as Webhooks.EventPayloadMap['release']
const githubToken = core.getInput('GITHUB_TOKEN')
const tagFilter = core.getInput('tag-filter') || undefined // Accept tag filter as an input
const octokit = github.getOctokit(githubToken)
const commentTemplate = core.getInput('comment-template')
const labelTemplate = core.getInput('label-template') || null
const skipLabelTemplate = core.getInput('skip-label') || null
// Fetch the releases with the optional tag filter applied
const { data: rawReleases } = await octokit.rest.repos.listReleases({
...github.context.repo,
per_page: 100,
})
// Get the current release tag or latest tag
const currentTag = payload?.release?.tag_name || rawReleases?.[0]?.tag_name
let releases = rawReleases
// Filter releases by the tag filter if provided
if (tagFilter) {
core.info(`Filtering releases by tag filter: ${tagFilter}`)
// Get the matching part of the current release tag
const regexMatch = currentTag.match(tagFilter)?.[0]
if (!regexMatch) {
core.error(`Current release tag ${currentTag} does not match the tag filter ${tagFilter}`)
return
}
core.info(`Matched string from filter: ${regexMatch}`)
releases = releases
.filter(release => {
const match = release.tag_name.match(regexMatch)?.[0]
return match
})
.slice(0, 2)
}
core.info(`Releases: ${JSON.stringify(releases, null, 2)}`)
if (releases.length < 2) {
if (!releases.length) {
core.error(`No releases found with the provided tag filter: '${tagFilter}'`)
return
}
core.info('first release')
return
}
const [currentRelease, priorRelease] = releases
core.info(`${priorRelease.tag_name}...${currentRelease.tag_name}`)
const {
data: { commits },
} = await octokit.rest.repos.compareCommits({
...github.context.repo,
base: priorRelease.tag_name,
head: currentRelease.tag_name,
})
if (!currentRelease.name) {
core.info('Current release has no name, will fall back to the tag name.')
}
const releaseLabel = currentRelease.name || currentRelease.tag_name
const comment = commentTemplate
.trim()
.split(releaseLinkTemplateRegex)
.join(`[${releaseLabel}](${currentRelease.html_url})`)
.split(releaseNameTemplateRegex)
.join(releaseLabel)
.split(releaseTagTemplateRegex)
.join(currentRelease.tag_name)
const parseLabels = (rawInput: string | null) =>
rawInput
?.split(releaseNameTemplateRegex)
.join(releaseLabel)
?.split(releaseTagTemplateRegex)
.join(currentRelease.tag_name)
?.split(',')
?.map(l => l.trim())
.filter(l => l)
const labels = parseLabels(labelTemplate)
const skipLabels = parseLabels(skipLabelTemplate)
const linkedIssuesPrs = new Set<number>()
await Promise.all(
commits.map(commit =>
(async () => {
const query = `
{
resource(url: "${payload.repository.html_url}/commit/${commit.sha}") {
... on Commit {
messageHeadlineHTML
messageBodyHTML
associatedPullRequests(first: 10) {
pageInfo {
hasNextPage
}
edges {
node {
bodyHTML
number
state
labels(first: 10) {
pageInfo {
hasNextPage
}
nodes {
name
}
}
timelineItems(itemTypes: [CONNECTED_EVENT, DISCONNECTED_EVENT], first: 100) {
pageInfo {
hasNextPage
}
nodes {
... on ConnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
... on DisconnectedEvent {
__typename
isCrossRepository
subject {
... on Issue {
number
}
}
}
}
}
}
}
}
}
}
}
`
const response: {
resource: null | {
messageHeadlineHTML: string
messageBodyHTML: string
associatedPullRequests: {
pageInfo: { hasNextPage: boolean }
edges: ReadonlyArray<{
node: {
bodyHTML: string
number: number
state: 'OPEN' | 'CLOSED' | 'MERGED'
labels: {
pageInfo: { hasNextPage: boolean }
nodes: ReadonlyArray<{
name: string
}>
}
timelineItems: {
pageInfo: { hasNextPage: boolean }
nodes: ReadonlyArray<{
__typename: 'ConnectedEvent' | 'DisconnectedEvent'
isCrossRepository: boolean
subject: {
number: number
}
}>
}
}
}>
}
}
} = await octokit.graphql(query)
if (!response.resource) {
return
}
// core.info(JSON.stringify(response.resource, null, 2))
core.info(`Checking commit: ${payload.repository.html_url}/commit/${commit.sha}`)
const associatedClosedPREdges = response.resource.associatedPullRequests.edges.filter(
e => e.node.state === 'MERGED',
)
if (associatedClosedPREdges.length) {
core.info(
` Associated Merged PRs:\n ${associatedClosedPREdges.map(pr => `${payload.repository.html_url}/pull/${pr.node.number}`).join('\n ')}`,
)
} else {
core.info(' No associated merged PRs')
}
const html = [
response.resource.messageHeadlineHTML,
response.resource.messageBodyHTML,
...associatedClosedPREdges.map(pr => pr.node.bodyHTML),
].join(' ')
for (const match of html.matchAll(closesMatcher)) {
const [, num] = match
linkedIssuesPrs.add(parseInt(num, 10))
core.info(
` Linked issue/PR from closesMatcher: ${payload.repository.html_url}/pull/${num}`,
)
}
if (response.resource.associatedPullRequests.pageInfo.hasNextPage) {
core.warning(`Too many PRs associated with ${commit.sha}`)
}
const seen = new Set<number>()
for (const associatedPR of associatedClosedPREdges) {
if (associatedPR.node.timelineItems.pageInfo.hasNextPage) {
core.warning(`Too many links for #${associatedPR.node.number}`)
}
if (associatedPR.node.labels.pageInfo.hasNextPage) {
core.warning(`Too many labels for #${associatedPR.node.number}`)
}
// a skip labels is present on this PR
if (
skipLabels?.some(l => associatedPR.node.labels.nodes.some(({ name }) => name === l))
) {
continue
}
linkedIssuesPrs.add(associatedPR.node.number)
core.info(
` Linked issue/PR from associated PR: ${payload.repository.html_url}/pull/${associatedPR.node.number}`,
)
// These are sorted by creation date in ascending order. The latest event for a given issue/PR is all we need
// ignore links that aren't part of this repo
const links = associatedPR.node.timelineItems.nodes
.filter(node => !node.isCrossRepository)
.reverse()
for (const link of links) {
if (seen.has(link.subject.number)) {
continue
}
if (link.__typename == 'ConnectedEvent') {
linkedIssuesPrs.add(link.subject.number)
core.info(
`Linked issue/PR from connected event: ${payload.repository.html_url}/pull/${link.subject.number}`,
)
}
seen.add(link.subject.number)
}
}
})(),
),
)
core.info(
`Final issues/PRs to be commented on: \n${Array.from(linkedIssuesPrs)
.map(num => ` ${payload.repository.html_url}/pull/${num}`)
.join('\n')}`,
)
const requests: Array<Promise<unknown>> = []
for (const issueNumber of linkedIssuesPrs) {
const baseRequest = {
...github.context.repo,
issue_number: issueNumber,
}
if (comment) {
const commentRequest = {
...baseRequest,
body: comment,
}
// Check if issue is locked or not
const { data: issue } = await octokit.rest.issues.get(baseRequest)
let createCommentPromise: () => Promise<void>
if (!issue.locked) {
createCommentPromise = async () => {
try {
await octokit.rest.issues.createComment(commentRequest)
} catch (error) {
core.error(error as Error)
core.error(
`Failed to comment on issue/PR: ${issueNumber}. ${payload.repository.html_url}/pull/${issueNumber}`,
)
}
}
} else {
core.info(
`Issue/PR is locked: ${issueNumber}. Unlocking, commenting, and re-locking. ${payload.repository.html_url}/pull/${issueNumber}`,
)
createCommentPromise = async () => {
try {
core.debug(`Unlocking issue/PR: ${issueNumber}`)
await octokit.rest.issues.unlock(baseRequest)
core.debug(`Commenting on issue/PR: ${issueNumber}`)
await octokit.rest.issues.createComment(commentRequest)
core.debug(`Re-locking issue/PR: ${issueNumber}`)
await octokit.rest.issues.lock(baseRequest)
} catch (error) {
core.error(error as Error)
core.error(
`Failed to unlock, comment, and re-lock issue/PR: ${issueNumber}. ${payload.repository.html_url}/pull/${issueNumber}`,
)
}
}
}
requests.push(createCommentPromise())
}
if (labels) {
const request = {
...baseRequest,
labels,
}
// core.info(JSON.stringify(request, null, 2))
requests.push(octokit.rest.issues.addLabels(request))
}
}
await Promise.all(requests)
} catch (error) {
core.error(error as Error)
core.setFailed((error as Error).message)
}
})()

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es2020.string"],
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"downlevelIteration": true,
"skipLibCheck": true,
},
"exclude": ["src/**/*.test.ts"]
}

View File

@@ -1,18 +0,0 @@
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)

View File

@@ -1,74 +0,0 @@
# docs: https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: github-actions
directories:
- /
- /.github/workflows
- /.github/actions/* # Not working until resolved: https://github.com/dependabot/dependabot-core/issues/6345
- /.github/actions/setup
target-branch: beta
schedule:
interval: monthly
timezone: America/Detroit
time: '06:00'
groups:
github_actions:
patterns:
- '*'
- package-ecosystem: npm
directory: /
target-branch: beta
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:
- minor
- patch
patterns:
- '*'
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*'

3977
.github/pnpm-lock.yaml generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
packages:
- 'actions/*'

View File

@@ -1,26 +0,0 @@
name: lock-issues
on:
schedule:
# Run nightly at 12am EST
- cron: '0 4 * * *'
workflow_dispatch:
permissions:
issues: write
jobs:
lock_issues:
runs-on: ubuntu-latest
steps:
- name: Lock issues
uses: dessant/lock-threads@v5
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.
Please open a new issue if this issue persists with any additional detail.

View File

@@ -27,7 +27,7 @@ jobs:
with:
filters: |
needs_build:
- '.github/workflows/main.yml'
- '.github/workflows/**'
- 'packages/**'
- 'test/**'
- 'pnpm-lock.yaml'
@@ -61,7 +61,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9.7.0
version: 8
run_install: false
- name: Get pnpm store directory
@@ -116,7 +116,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9.7.0
version: 8
run_install: false
- name: Restore build
@@ -201,7 +201,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9.7.0
version: 8
run_install: false
- name: Restore build
@@ -242,7 +242,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9.7.0
version: 8
run_install: false
- name: Restore build
@@ -286,7 +286,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9.7.0
version: 8
run_install: false
- name: Restore build
@@ -327,7 +327,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9.7.0
version: 8
run_install: false
- name: Restore build

View File

@@ -1,32 +0,0 @@
name: post-release
on:
release:
types:
- published
workflow_dispatch:
jobs:
post_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Only needed if debugging on a branch other than default
# ref: ${{ github.event.release.target_commitish || github.ref }}
- run: echo "npm_version=$(npm pkg get version | tr -d '"')" >> "$GITHUB_ENV"
- uses: ./.github/actions/release-commenter
continue-on-error: true
env:
ACTIONS_STEP_DEBUG: true
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag-filter: 'v\d'
# Change to blank to disable commenting
# comment-template: ''
comment-template: |
🚀 This is included in version {release_link}

View File

@@ -1,29 +0,0 @@
name: triage
on:
issues:
types:
- opened
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
issues: write
jobs:
triage:
name: nissuer
if: false # Disable after adjusting scenarios which this should be applied
runs-on: ubuntu-latest
steps:
- uses: balazsorban44/nissuer@1.10.0
with:
label-area-prefix: ""
label-area-match: "name"
label-area-section: 'Which area\(s\) are affected\? \(Select all that apply\)(.*)### Environment Info'
reproduction-comment: '.github/comments/invalid-reproduction.md'
reproduction-blocklist: 'github.com/\\w*/?$,github.com$'
reproduction-link-section: '### Link to the code that reproduces this issue(.*)### Reproduction Steps'
reproduction-invalid-label: 'invalid-reproduction'
reproduction-issue-labels: 'status: needs-triage,'

4
.gitignore vendored
View File

@@ -4,9 +4,6 @@ dist
/.idea/*
!/.idea/runConfigurations
# Custom actions
!.github/actions/**/dist
test-results
.devcontainer
.localstack
@@ -137,6 +134,7 @@ out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/

View File

@@ -1,98 +1,3 @@
## [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)
### Features
* add new option to disable JOI validation ([#8067](https://github.com/payloadcms/payload/issues/8067)) ([28a0650](https://github.com/payloadcms/payload/commit/28a065072fcad2dc768e44d79609eb5ab8a3fdfd))
### Bug Fixes
* **db-postgres:** localized items in arrays with versions ([#8334](https://github.com/payloadcms/payload/issues/8334)) ([c86526b](https://github.com/payloadcms/payload/commit/c86526b5c81ff484e66fbe6e7c727fdcc1f93c77))
* **db-postgres:** querying on array within a relationship field ([#8153](https://github.com/payloadcms/payload/issues/8153)) ([170ea5b](https://github.com/payloadcms/payload/commit/170ea5badcff154514b8166ac92177d89a3fa5f8))
* **db-postgres:** sanitize tab/group path for table name ([#8010](https://github.com/payloadcms/payload/issues/8010)) ([ba7a043](https://github.com/payloadcms/payload/commit/ba7a043a99f58fad39a62ac471eeb7309a39bba0))
* treat empty strings as null / undefined for `exists` queries ([#8336](https://github.com/payloadcms/payload/issues/8336)) ([31d0b30](https://github.com/payloadcms/payload/commit/31d0b309fe5df1e37ed2a938959c1ef87834d987)), closes [#7714](https://github.com/payloadcms/payload/issues/7714)
## [2.28.0](https://github.com/payloadcms/payload/compare/v2.27.0...v2.28.0) (2024-09-04)
### Features
* collections can use custom database operations ([#7675](https://github.com/payloadcms/payload/issues/7675)) ([6ba293c](https://github.com/payloadcms/payload/commit/6ba293c0f84f91bf89cf089a20e47de130013ebb))
### Bug Fixes
* **db-postgres:** migration exit codes ([#7873](https://github.com/payloadcms/payload/issues/7873)) ([25e9bc6](https://github.com/payloadcms/payload/commit/25e9bc62dbcbabcb3619cf83e3dc0110e0a4cabf)), closes [#7031](https://github.com/payloadcms/payload/issues/7031)
* **db-postgres:** query hasMany text/number in array/blocks ([#8033](https://github.com/payloadcms/payload/issues/8033)) ([96a624a](https://github.com/payloadcms/payload/commit/96a624ad5c5259b197b4ca793d8419d1e827de9c))
* **plugin-cloud:** better logging on static handler ([#7924](https://github.com/payloadcms/payload/issues/7924)) ([1f09348](https://github.com/payloadcms/payload/commit/1f0934877ce5aabb771c936c3677a26d2ef006ec))
## [2.27.0](https://github.com/payloadcms/payload/compare/v2.26.0...v2.27.0) (2024-08-26)
### Features
* add support for custom image size file names ([#7637](https://github.com/payloadcms/payload/issues/7637)) ([f976270](https://github.com/payloadcms/payload/commit/f97627092cabe4eabbebefa75afc53579188386b))
* upgrade react-toastify dependency, and upgrade to pnpm v9 in our monorepo ([#7667](https://github.com/payloadcms/payload/issues/7667)) ([94d18e8](https://github.com/payloadcms/payload/commit/94d18e8d747588efce225cde0b621db9b513e7c1))
### Bug Fixes
* update state of field if either `valid` status or `errorMessage` changes ([#7632](https://github.com/payloadcms/payload/issues/7632)) ([c624eea](https://github.com/payloadcms/payload/commit/c624eea0d868938f4603860fa25be3df580ba7fe)), closes [#6413](https://github.com/payloadcms/payload/issues/6413)
## [2.26.0](https://github.com/payloadcms/payload/compare/v2.25.0...v2.26.0) (2024-08-09)
### Features
* adds classnames to edit, list views ([#7595](https://github.com/payloadcms/payload/issues/7595)) ([7f39afa](https://github.com/payloadcms/payload/commit/7f39afa1928b118451138e811ea71a04fce021d5))
* adds upload's relationship thumbnail ([#5015](https://github.com/payloadcms/payload/issues/5015)) ([39e110e](https://github.com/payloadcms/payload/commit/39e110e6331efff0ca8ca7174780076243a016de))
* **ui:** expose custom errors in delete many ([#7439](https://github.com/payloadcms/payload/issues/7439)) ([3e780b9](https://github.com/payloadcms/payload/commit/3e780b98155550f877021996dd094ba435dff81b))
### Bug Fixes
* **db-postgres:** localized array inside blocks field ([#7458](https://github.com/payloadcms/payload/issues/7458)) ([a308d63](https://github.com/payloadcms/payload/commit/a308d6384f9724c5ff330382070a5803fbcf167c)), closes [#5240](https://github.com/payloadcms/payload/issues/5240)
* deprecated `inflight` package ([#6558](https://github.com/payloadcms/payload/issues/6558)) ([eca1517](https://github.com/payloadcms/payload/commit/eca1517237c78983c192f4bafa92a86d94a0de9e)), closes [#6492](https://github.com/payloadcms/payload/issues/6492)
* enable `relationship` & `upload` field population in `versions` ([#7533](https://github.com/payloadcms/payload/issues/7533)) ([9865ae9](https://github.com/payloadcms/payload/commit/9865ae998b9aeb5d72724023976bb203133e19ff))
* filtering by non-poly `relationships` with `not_equals` operator ([#7573](https://github.com/payloadcms/payload/issues/7573)) ([efa56ce](https://github.com/payloadcms/payload/commit/efa56cefc15a48cd45b3aaba2eddacca79e1be30)), closes [#5212](https://github.com/payloadcms/payload/issues/5212) [#6278](https://github.com/payloadcms/payload/issues/6278)
* filtering by polymorphic `relationships` with `drafts` enabled ([#7565](https://github.com/payloadcms/payload/issues/7565)) ([907d7d1](https://github.com/payloadcms/payload/commit/907d7d1d3a89ed22bb991a1f238bb77d54e3e173)), closes [#6880](https://github.com/payloadcms/payload/issues/6880)
* retained date milliseconds ([#7393](https://github.com/payloadcms/payload/issues/7393)) ([9c9e689](https://github.com/payloadcms/payload/commit/9c9e6896a502de209c6cccf63cc5cfc0f0143bf3)), closes [#6108](https://github.com/payloadcms/payload/issues/6108)
* prevents `hasMany` text going outside of input boundaries ([#7454](https://github.com/payloadcms/payload/issues/7454)) ([1a0ef48](https://github.com/payloadcms/payload/commit/1a0ef4824b3d6548d36e7f28a2030640361c0655)), closes [#6034](https://github.com/payloadcms/payload/issues/6034)
* previousValue missing from ValidateOptions type ([#6931](https://github.com/payloadcms/payload/issues/6931)) ([fca5a40](https://github.com/payloadcms/payload/commit/fca5a404dbf3b440b428e55cf5e03db647f9a453))
* render singular label for `ArrayCell` when length is 1 ([#7585](https://github.com/payloadcms/payload/issues/7585)) ([fc4d24a](https://github.com/payloadcms/payload/commit/fc4d24aa8889ac9be76059a92478d5532b142b5c)), closes [#6099](https://github.com/payloadcms/payload/issues/6099)
## [2.25.0](https://github.com/payloadcms/payload/compare/v2.24.2...v2.25.0) (2024-07-26)

View File

@@ -99,10 +99,6 @@ 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)

View File

@@ -30,8 +30,7 @@ It's often best practice to write your Collections in separate files and then im
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`dbName`** | Custom table or collection name depending on the database adapter. Auto-generated from slug if not defined. |
| **`db`** | Set custom database operations for this Collection. [More](/docs/database/overview#collection-operations) |
| **`dbName`** | Custom table or collection name depending on the database adapter. Auto-generated from slug if not defined.
_\* An asterisk denotes that a property is required._

View File

@@ -24,8 +24,8 @@ export default buildConfig({
// collections go here
],
localization: {
locales: ['en', 'es', 'de'], // required
defaultLocale: 'en', // required
locales: ['en', 'es', 'de'],
defaultLocale: 'en',
fallback: true,
},
})
@@ -54,7 +54,7 @@ export default buildConfig({
rtl: true,
},
],
defaultLocale: 'en', // required
defaultLocale: 'en',
fallback: true,
},
})
@@ -87,7 +87,7 @@ export default buildConfig({
code: 'nb',
},
],
defaultLocale: 'en', // required
defaultLocale: 'en',
fallback: true,
},
})

View File

@@ -70,105 +70,4 @@ export default buildConfig({
}
}),
})
```
## Collection Operations
To configure Collection database operations in your Payload application, your Collection config has methods that can override default database operations for that Collection.
The override methods receive arguments useful for augmenting operations such as Field data, the collection slug, and the req.
Here is an example:
```ts
import type { CollectionConfig } from 'payload/types'
export const Collection: CollectionConfig => {
return {
slug: 'collection-db-operations',
db: {
// Create a document in a custom db
create: async ({ collection, data, req }) => {
const doc = await fetch(`https://example.com/api/${collection}/create`, {
method: "POST",
body: JSON.stringify(data),
headers: {
'x-app-user': `payload_${req.payload.user}`,
'Content-Type': 'application/json'
}
}).then(response => response.json())
return doc
},
// Delete a document in a custom db
deleteOne: async ({ collection, data, req }) => {
const docs = await fetch(`https://example.com/api/${collection}/delete/${data.id}`, {
method: 'DELETE',
headers: {
'x-app-user': `payload_${req.payload.user}`
}
}).then(response => response.json())
return docs
},
// Delete many documents in a custom db
deleteMany: async ({ collection, data, req }) => {
const docs = await fetch(`https://example.com/api/${collection}/delete`, {
method: 'DELETE'
headers: {
'x-app-user': `payload_${req.payload.user}`
}
body: JSON.stringify(data),
}).then(response => response.json())
return docs
},
// Find documents in a custom db
find: async ({ collection, data, req, where, limit }) => {
const docs = await fetch(`https://example.com/api/${collection}/find`, {
headers: {
'x-app-user': `payload_${req.payload.user}`
}
body: JSON.stringify({data, where, limit}),
}).then(response => response.json())
return { docs }
},
// Find one document in a custom db
findOne: async ({ collection, data, req }) => {
const doc = await fetch(`https://example.com/api/${collection}/find/${data.id}`, {
headers: {
'x-app-user': `payload_${req.payload.user}`
}
}).then(response => response.json())
return doc
},
// Update one document in an custom db
updateOne: async ({ collection, data, req }) => {
const doc = await fetch(`https://example.com/api/${collection}/update/${data.id}`, {
method: 'PUT',
body: JSON.stringify(data),
headers: {
'x-app-user': `payload_${req.payload.user}`,
'Content-Type': 'application/json'
}
}).then(response => response.json())
return { ...doc, updated: true }
},
},
fields: [
{
name: 'name',
type: 'text',
},
],
}
}
```
```

View File

@@ -49,7 +49,6 @@ caption="Admin panel screenshot of an Upload field"
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |

View File

@@ -2,13 +2,13 @@
title: Hooks Overview
label: Overview
order: 10
desc: Hooks allow you to add your own logic to Payload, including integrating with third-party APIs, adding auto-generated data, or modifying Payload's base functionality.
desc: Hooks allow you to add your own logic to Payload, including integrating with third-party APIs, adding auto-generated data, or modifing Payload's base functionality.
keywords: hooks, overview, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner type="info">
Hooks are powerful ways to tie into existing Payload actions in order to add your own logic like
integrating with third-party APIs, adding auto-generated data, or modifying Payload's base
integrating with third-party APIs, adding auto-generated data, or modifing Payload's base
functionality.
</Banner>

View File

@@ -144,10 +144,6 @@ export default addLastModified
### Available Plugins
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.
You can discover existing plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin).
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).

View File

@@ -76,7 +76,7 @@ The following custom endpoints are automatically opened for you:
| Endpoint | Method | Description |
| --- | --- | --- |
| `/api/stripe/rest` | `POST` | Proxies the [Stripe REST API](https://stripe.com/docs/api) behind [Payload access control](https://payloadcms.com/docs/access-control/overview) and returns the result. See the [REST Proxy](#stripe-rest-proxy) section for more details. |
| `/stripe/webhooks` | `POST` | Handles all Stripe webhook events |
| `/api/stripe/webhooks` | `POST` | Handles all Stripe webhook events |
##### Stripe REST Proxy
@@ -114,13 +114,13 @@ const res = await fetch(`/api/stripe/rest`, {
Development:
1. Login using Stripe cli `stripe login`
1. Forward events to localhost `stripe listen --forward-to localhost:3000/stripe/webhooks`
1. Forward events to localhost `stripe listen --forward-to localhost:3000/api/stripe/webhooks`
1. Paste the given secret into your `.env` file as `STRIPE_WEBHOOKS_ENDPOINT_SECRET`
Production:
1. Login and [create a new webhook](https://dashboard.stripe.com/test/webhooks/create) from the Stripe dashboard
1. Paste `YOUR_DOMAIN_NAME/stripe/webhooks` as the "Webhook Endpoint URL"
1. Paste `YOUR_DOMAIN_NAME/api/stripe/webhooks` as the "Webhook Endpoint URL"
1. Select which events to broadcast
1. Paste the given secret into your `.env` file as `STRIPE_WEBHOOKS_ENDPOINT_SECRET`
1. Then, handle these events using the `webhooks` portion of this plugin's config:

View File

@@ -47,7 +47,6 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config). |
| **`externalFileHeaderFilter`** | Accepts existing headers and can filter/modify them. |
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
@@ -168,22 +167,6 @@ When an uploaded image is smaller than the defined image size, we have 3 options
Use the `withoutEnlargement` prop to change this.
</Banner>
#### Custom file name per size
Each image size supports a `generateImageName` function that can be used to generate a custom file name for the resized image.
This function receives the original file name, the resize name, the extension, height and width as arguments.
```ts
{
name: 'thumbnail',
width: 400,
height: 300,
generateImageName: ({ height, sizeName, extension, width }) => {
return `custom-${sizeName}-${height}-${width}.${extension}`
},
}
```
### Crop and Focal Point Selector
This feature is only available for image file types.

View File

@@ -91,7 +91,7 @@
"prompts": "2.4.2",
"qs": "6.11.2",
"read-stream": "^2.1.1",
"rimraf": "4.4.1",
"rimraf": "3.0.2",
"semver": "^7.5.4",
"shelljs": "0.8.5",
"simple-git": "^3.20.0",
@@ -120,9 +120,8 @@
},
"engines": {
"node": ">=14",
"pnpm": ">=9.7.0"
"pnpm": ">=8"
},
"packageManager": "pnpm@9.7.0",
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write"

View File

@@ -33,15 +33,15 @@
"md5": "2.3.0",
"mini-css-extract-plugin": "1.6.2",
"path-browserify": "1.0.1",
"postcss": "8.4.47",
"postcss": "8.4.31",
"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.6",
"swc-minify-webpack-plugin": "^2.1.3",
"terser-webpack-plugin": "^5.3.10",
"swc-loader": "^0.2.3",
"swc-minify-webpack-plugin": "^2.1.0",
"terser-webpack-plugin": "^5.3.6",
"url-loader": "4.1.1",
"webpack": "^5.78.0",
"webpack-bundle-analyzer": "^4.8.0",

View File

@@ -1,5 +1,6 @@
import slugify from '@sindresorhus/slugify'
import arg from 'arg'
import commandExists from 'command-exists'
import type { CliArgs, PackageManager } from './types'
@@ -67,7 +68,7 @@ export class Main {
const template = await parseTemplate(this.args, validTemplates)
const projectDir = projectName === '.' ? process.cwd() : `./${slugify(projectName)}`
const packageManager = getPackageManager(this.args)
const packageManager = await getPackageManager(this.args)
if (template.type !== 'plugin') {
const dbDetails = await selectDb(this.args, projectName)
@@ -108,7 +109,7 @@ export class Main {
}
}
function getPackageManager(args: CliArgs): PackageManager {
async function getPackageManager(args: CliArgs): Promise<PackageManager> {
let packageManager: PackageManager = 'npm'
if (args['--use-npm']) {
@@ -118,22 +119,15 @@ function getPackageManager(args: CliArgs): PackageManager {
} else if (args['--use-pnpm']) {
packageManager = 'pnpm'
} else {
packageManager = getEnvironmentPackageManager()
try {
if (await commandExists('yarn')) {
packageManager = 'yarn'
} else if (await commandExists('pnpm')) {
packageManager = 'pnpm'
}
} catch (error: unknown) {
packageManager = 'npm'
}
}
return packageManager
}
function getEnvironmentPackageManager(): PackageManager {
const userAgent = process.env.npm_config_user_agent || ''
if (userAgent.startsWith('yarn')) {
return 'yarn'
}
if (userAgent.startsWith('pnpm')) {
return 'pnpm'
}
return 'npm'
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "1.7.3",
"version": "1.7.1",
"description": "The officially supported MongoDB database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -3,7 +3,7 @@ import type { PathToQuery } from 'payload/database'
import type { Field } from 'payload/types'
import type { Operator } from 'payload/types'
import ObjectIdImport from 'bson-objectid'
import objectID from 'bson-objectid'
import mongoose from 'mongoose'
import { getLocalizedPaths } from 'payload/database'
import { fieldAffectsData } from 'payload/types'
@@ -14,8 +14,6 @@ import type { MongooseAdapter } from '..'
import { operatorMap } from './operatorMap'
import { sanitizeQueryValue } from './sanitizeQueryValue'
const ObjectId = ObjectIdImport
type SearchParam = {
path?: string
rawQuery?: unknown
@@ -197,20 +195,16 @@ export async function buildSearchParam({
if (field.type === 'relationship' || field.type === 'upload') {
let hasNumberIDRelation
let multiIDCondition = '$or'
if (operatorKey === '$ne') multiIDCondition = '$and'
const result = {
value: {
[multiIDCondition]: [{ [path]: { [operatorKey]: formattedValue } }],
$or: [{ [path]: { [operatorKey]: formattedValue } }],
},
}
if (typeof formattedValue === 'string') {
if (mongoose.Types.ObjectId.isValid(formattedValue)) {
result.value[multiIDCondition].push({
[path]: { [operatorKey]: ObjectId(formattedValue) },
})
result.value.$or.push({ [path]: { [operatorKey]: objectID(formattedValue) } })
} else {
;(Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]).forEach(
(relationTo) => {
@@ -231,13 +225,11 @@ export async function buildSearchParam({
)
if (hasNumberIDRelation)
result.value[multiIDCondition].push({
[path]: { [operatorKey]: parseFloat(formattedValue) },
})
result.value.$or.push({ [path]: { [operatorKey]: parseFloat(formattedValue) } })
}
}
if (result.value[multiIDCondition].length > 1) {
if (result.value.$or.length > 1) {
return result
}
}

View File

@@ -55,30 +55,6 @@ 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,
@@ -126,16 +102,8 @@ export const sanitizeQueryValue = ({
}
}
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 === 'number' && typeof formattedValue === 'string') {
formattedValue = Number(val)
}
if (field.type === 'date' && typeof val === 'string' && operator !== 'exists') {
@@ -221,12 +189,6 @@ export const sanitizeQueryValue = ({
$regex: formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
}
}
if (operator === 'exists') {
formattedValue = formattedValue === 'true' || formattedValue === true
return buildExistsQuery(formattedValue, path)
}
}
if (

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.8.8",
"version": "0.8.5",
"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.23.2-df9e596",
"drizzle-kit": "0.23.1-7816536",
"drizzle-orm": "0.32.1",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "10.0.0"
"uuid": "9.0.0"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",

View File

@@ -1,14 +1,14 @@
import type { Payload } from 'payload'
import type { Connect } from 'payload/database'
import { sql } from 'drizzle-orm'
import { eq, 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,
@@ -78,10 +78,77 @@ 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
) {
await pushDevSchema(this)
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,
this.schemaName ? [this.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()
// 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'))
}
}

View File

@@ -1,7 +1,7 @@
import type { Count } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import { sql, count as sqlCount } from 'drizzle-orm'
import { sql } from 'drizzle-orm'
import toSnakeCase from 'to-snake-case'
import type { ChainedMethods } from './find/chainMethods'
@@ -51,11 +51,8 @@ export const count: Count = async function count(
methods: selectCountMethods,
query: db
.select({
count:
selectCountMethods.length > 0
? sql<number>`count
(DISTINCT ${this.tables[tableName].id})`
: sqlCount(),
count: sql<number>`count
(DISTINCT ${this.tables[tableName].id})`,
})
.from(table)
.where(where),

View File

@@ -1,5 +1,5 @@
/* eslint-disable no-restricted-syntax, no-await-in-loop */
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { CreateMigration } from 'payload/database'
import fs from 'fs'
@@ -43,11 +43,11 @@ const getDefaultDrizzleSnapshot = (): DrizzleSnapshotJSON => ({
schemas: {},
tables: {},
},
dialect: 'postgresql',
dialect: 'pg',
enums: {},
prevId: '00000000-0000-0000-0000-00000000000',
schemas: {},
sequences: {},
schemas: {},
tables: {},
version: '7',
})
@@ -61,7 +61,7 @@ export const createMigration: CreateMigration = async function createMigration(
fs.mkdirSync(dir)
}
const { generateDrizzleJson, generateMigration, upPgSnapshot } = require('drizzle-kit/api')
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
@@ -99,11 +99,6 @@ 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)

View File

@@ -75,7 +75,6 @@ export const buildFindManyArgs = ({
depth,
fields,
path: '',
tablePath: '',
topLevelArgs: result,
topLevelTableName: tableName,
})

View File

@@ -1,7 +1,7 @@
import type { FindArgs } from 'payload/database'
import type { Field, PayloadRequest, TypeWithID } from 'payload/types'
import { inArray, sql, count as sqlCount } from 'drizzle-orm'
import { inArray, sql } from 'drizzle-orm'
import type { PostgresAdapter } from '../types'
import type { ChainedMethods } from './chainMethods'
@@ -143,11 +143,8 @@ export const findMany = async function find({
methods: selectCountMethods,
query: db
.select({
count:
selectCountMethods.length > 0
? sql<number>`count
(DISTINCT ${adapter.tables[tableName].id})`
: sqlCount(),
count: sql<number>`count
(DISTINCT ${adapter.tables[tableName].id})`,
})
.from(table)
.where(where),

View File

@@ -15,7 +15,6 @@ type TraverseFieldArgs = {
depth?: number
fields: Field[]
path: string
tablePath: string
topLevelArgs: Record<string, unknown>
topLevelTableName: string
}
@@ -28,7 +27,6 @@ export const traverseFields = ({
depth,
fields,
path,
tablePath,
topLevelArgs,
topLevelTableName,
}: TraverseFieldArgs) => {
@@ -40,7 +38,6 @@ export const traverseFields = ({
currentArgs,
currentTableName,
depth,
tablePath,
fields: field.fields,
path,
topLevelArgs,
@@ -53,7 +50,6 @@ export const traverseFields = ({
if (field.type === 'tabs') {
field.tabs.forEach((tab) => {
const tabPath = tabHasName(tab) ? `${path}${tab.name}_` : path
const tabTablePath = tabHasName(tab) ? `${tablePath}${toSnakeCase(tab.name)}_` : tablePath
traverseFields({
_locales,
@@ -63,7 +59,6 @@ export const traverseFields = ({
depth,
fields: tab.fields,
path: tabPath,
tablePath: tabTablePath,
topLevelArgs,
topLevelTableName,
})
@@ -84,7 +79,7 @@ export const traverseFields = ({
}
const arrayTableName = adapter.tableNameMap.get(
`${currentTableName}_${tablePath}${toSnakeCase(field.name)}`,
`${currentTableName}_${path}${toSnakeCase(field.name)}`,
)
const arrayTableNameWithLocales = `${arrayTableName}${adapter.localesSuffix}`
@@ -100,7 +95,6 @@ export const traverseFields = ({
depth,
fields: field.fields,
path: '',
tablePath: '',
topLevelArgs,
topLevelTableName,
})
@@ -153,7 +147,6 @@ export const traverseFields = ({
currentArgs: withBlock,
currentTableName: tableName,
depth,
tablePath: '',
fields: block.fields,
path: '',
topLevelArgs,
@@ -170,7 +163,6 @@ export const traverseFields = ({
adapter,
currentArgs,
currentTableName,
tablePath: `${tablePath}${toSnakeCase(field.name)}_`,
depth,
fields: field.fields,
path: `${path}${field.name}_`,

View File

@@ -37,12 +37,20 @@ import { updateOne } from './update'
import { updateGlobal } from './updateGlobal'
import { updateGlobalVersion } from './updateGlobalVersion'
import { updateVersion } from './updateVersion'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
export type { MigrateDownArgs, MigrateUpArgs } from './types'
export function postgresAdapter(args: Args): PostgresAdapterResult {
const postgresIDType = args.idType || 'serial'
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text'
let adapterSchema: PostgresAdapter['pgSchema']
if (args.schemaName) {
adapterSchema = pgSchema(args.schemaName)
} else {
adapterSchema = { enum: pgEnum, table: pgTable }
}
function adapter({ payload }: { payload: Payload }) {
const migrationDir = findMigrationDir(args.migrationDir)
@@ -55,7 +63,7 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
idType: postgresIDType,
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger,
pgSchema: undefined,
pgSchema: adapterSchema,
pool: undefined,
poolOptions: args.pool,
push: args.push,

View File

@@ -2,7 +2,6 @@
import type { Init } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
import type { PostgresAdapter } from './types'
@@ -11,12 +10,6 @@ import { buildTable } from './schema/build'
import { createTableName } from './schema/createTableName'
export const init: Init = async function init(this: PostgresAdapter) {
if (this.schemaName) {
this.pgSchema = pgSchema(this.schemaName)
} else {
this.pgSchema = { enum: pgEnum, table: pgTable }
}
if (this.payload.config.localization) {
this.enums.enum__locales = this.pgSchema.enum(
'_locales',

View File

@@ -27,7 +27,7 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
let latestBatch = 0
let migrationsInDB = []
const hasMigrationTable = await migrationTableExists(this)
const hasMigrationTable = await migrationTableExists(this.drizzle)
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/api')
const { generateDrizzleJson } = require('drizzle-kit/payload')
const start = Date.now()
const req = { payload } as PayloadRequest
@@ -110,6 +110,5 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n
err,
msg: parseError(err, `Error running migration ${migration.name}`),
})
process.exit(1)
}
}

View File

@@ -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)
const tableExists = await migrationTableExists(this.drizzle)
if (tableExists) {
await payload.delete({
id: migration.id,

View File

@@ -79,7 +79,6 @@ export async function migrateFresh(
err,
msg: parseError(err, `Error running migration ${migration.name}. Rolling back`),
})
process.exit(1)
}
}
}

View File

@@ -51,7 +51,7 @@ export async function migrateRefresh(this: PostgresAdapter) {
msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)`,
})
const tableExists = await migrationTableExists(this)
const tableExists = await migrationTableExists(this.drizzle)
if (tableExists) {
await payload.delete({
collection: 'payload-migrations',
@@ -98,7 +98,6 @@ export async function migrateRefresh(this: PostgresAdapter) {
err,
msg: parseError(err, `Error running migration ${migration.name}. Rolling back.`),
})
process.exit(1)
}
}
}

View File

@@ -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)
const tableExists = await migrationTableExists(this.drizzle)
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)
const tableExists = await migrationTableExists(this.drizzle)
if (tableExists) {
try {
await payload.delete({

View File

@@ -14,7 +14,7 @@ export async function migrateStatus(this: PostgresAdapter): Promise<void> {
})
let existingMigrations = []
const hasMigrationTable = await migrationTableExists(this)
const hasMigrationTable = await migrationTableExists(this.drizzle)
if (hasMigrationTable) {
;({ existingMigrations } = await getMigrations({ payload }))

View File

@@ -261,10 +261,10 @@ export const getTableColumnFromPath = ({
tableType = 'numbers'
columnName = 'number'
}
newTableName = `${rootTableName}_${tableType}`
newTableName = `${tableName}_${tableType}`
const joinConstraints = [
eq(adapter.tables[rootTableName].id, adapter.tables[newTableName].parent),
like(adapter.tables[newTableName].path, `${constraintPath}${field.name}`),
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName].path, `${constraintPath}${field.name}`),
]
if (locale && field.localized && adapter.payload.config.localization) {
@@ -298,12 +298,10 @@ export const getTableColumnFromPath = ({
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
)
const arrayParentTable = aliasTable || adapter.tables[tableName]
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[tableName].id, adapter.tables[newTableName]._parentID),
eq(adapter.tables[newTableName]._locale, locale),
)
if (locale !== 'all') {
@@ -314,7 +312,10 @@ export const getTableColumnFromPath = ({
})
}
} else {
joins[newTableName] = eq(arrayParentTable.id, adapter.tables[newTableName]._parentID)
joins[newTableName] = eq(
adapter.tables[tableName].id,
adapter.tables[newTableName]._parentID,
)
}
return getTableColumnFromPath({
adapter,

View File

@@ -3,7 +3,6 @@ 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'
@@ -175,7 +174,6 @@ export async function parseParams({
const sanitizedQueryValue = sanitizeQueryValue({
adapter,
field,
isUUID: table?.[columnName] instanceof PgUUID,
operator,
relationOrPath,
val,

View File

@@ -1,14 +1,12 @@
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
@@ -17,7 +15,6 @@ type SanitizeQueryValueArgs = {
export const sanitizeQueryValue = ({
adapter,
field,
isUUID,
operator: operatorArg,
relationOrPath,
val,
@@ -67,16 +64,6 @@ 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') {

View File

@@ -7,7 +7,7 @@ import type {
PgTableWithColumns,
UniqueConstraintBuilder,
} from 'drizzle-orm/pg-core'
import type { Field } from 'payload/types'
import { Field, fieldAffectsData } from 'payload/types'
import { relations } from 'drizzle-orm'
import {
@@ -20,12 +20,10 @@ 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'
@@ -53,17 +51,9 @@ 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>
@@ -86,7 +76,6 @@ export const buildTable = ({
tableName,
timestamps,
versions,
withinLocalizedArrayOrBlock,
}: Args): Result => {
const rootTableName = incomingRootTableName || tableName
const columns: Record<string, PgColumnBuilder> = baseColumns
@@ -135,7 +124,6 @@ export const buildTable = ({
rootTableIDColType: rootTableIDColType || idColType,
rootTableName,
versions,
withinLocalizedArrayOrBlock,
})
if (timestamps) {
@@ -340,28 +328,16 @@ export const buildTable = ({
if (relatedCollectionCustomIDType === 'number') colType = 'numeric'
if (relatedCollectionCustomIDType === 'text') colType = 'varchar'
const colName = `${relationTo}ID`
relationshipColumns[colName] = parentIDColumnMap[colType](`${formattedRelationTo}_id`)
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
`${formattedRelationTo}_id`,
)
relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
foreignKey({
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
columns: [cols[colName]],
columns: [cols[`${relationTo}ID`]],
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(
@@ -455,12 +431,5 @@ export const buildTable = ({
adapter.relations[`relations_${tableName}`] = tableRelations
return {
hasLocalizedManyNumberField,
hasLocalizedManyTextField,
hasLocalizedRelationshipField,
hasManyNumberField,
hasManyTextField,
relationsToBuild,
}
return { hasManyNumberField, hasManyTextField, relationsToBuild }
}

View File

@@ -56,11 +56,6 @@ 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 = {
@@ -95,7 +90,6 @@ export const traverseFields = ({
rootTableIDColType,
rootTableName,
versions,
withinLocalizedArrayOrBlock,
}: Args): Result => {
const throwValidationError = true
let hasLocalizedField = false
@@ -157,11 +151,7 @@ export const traverseFields = ({
switch (field.type) {
case 'text': {
if (field.hasMany) {
const isLocalized =
Boolean(field.localized && adapter.payload.config.localization) ||
withinLocalizedArrayOrBlock
if (isLocalized) {
if (field.localized) {
hasLocalizedManyTextField = true
}
@@ -190,12 +180,7 @@ export const traverseFields = ({
case 'number': {
if (field.hasMany) {
const isLocalized =
Boolean(field.localized && adapter.payload.config.localization) ||
withinLocalizedArrayOrBlock ||
forceLocalized
if (isLocalized) {
if (field.localized) {
hasLocalizedManyNumberField = true
}
@@ -282,12 +267,7 @@ export const traverseFields = ({
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
}
const isLocalized =
Boolean(field.localized && adapter.payload.config.localization) ||
withinLocalizedArrayOrBlock ||
forceLocalized
if (isLocalized) {
if (field.localized) {
baseColumns.locale = adapter.enums.enum__locales('locale').notNull()
baseExtraConfig.localeIdx = (cols) =>
index(`${selectTableName}_locale_idx`).on(cols.locale)
@@ -361,21 +341,13 @@ export const traverseFields = ({
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
}
const isLocalized =
Boolean(field.localized && adapter.payload.config.localization) ||
withinLocalizedArrayOrBlock ||
forceLocalized
if (isLocalized) {
if (field.localized && adapter.payload.config.localization) {
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,
@@ -392,21 +364,8 @@ 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
@@ -473,21 +432,13 @@ export const traverseFields = ({
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
}
const isLocalized =
Boolean(field.localized && adapter.payload.config.localization) ||
withinLocalizedArrayOrBlock ||
forceLocalized
if (isLocalized) {
if (field.localized && adapter.payload.config.localization) {
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,
@@ -504,21 +455,8 @@ 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
@@ -602,7 +540,6 @@ export const traverseFields = ({
rootTableIDColType,
rootTableName,
versions,
withinLocalizedArrayOrBlock,
})
if (groupHasLocalizedField) hasLocalizedField = true
@@ -646,7 +583,6 @@ export const traverseFields = ({
rootTableIDColType,
rootTableName,
versions,
withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized,
})
if (groupHasLocalizedField) hasLocalizedField = true
@@ -691,7 +627,6 @@ export const traverseFields = ({
rootTableIDColType,
rootTableName,
versions,
withinLocalizedArrayOrBlock,
})
if (tabHasLocalizedField) hasLocalizedField = true
@@ -736,7 +671,6 @@ export const traverseFields = ({
rootTableIDColType,
rootTableName,
versions,
withinLocalizedArrayOrBlock,
})
if (rowHasLocalizedField) hasLocalizedField = true
@@ -756,10 +690,7 @@ export const traverseFields = ({
relationships.add(field.relationTo)
}
if (
Boolean(field.localized && adapter.payload.config.localization) ||
withinLocalizedArrayOrBlock
) {
if (field.localized && adapter.payload.config.localization) {
hasLocalizedRelationshipField = true
}
break

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import type { NumberField } from 'payload/types'
type Args = {
@@ -5,29 +6,10 @@ type Args = {
locale?: string
numberRows: Record<string, unknown>[]
ref: Record<string, unknown>
withinArrayOrBlockLocale?: string
}
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)
}
export const transformHasManyNumber = ({ field, locale, numberRows, ref }: Args) => {
const result = numberRows.map(({ number }) => number)
if (locale) {
ref[field.name][locale] = result

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import type { TextField } from 'payload/types'
type Args = {
@@ -5,29 +6,10 @@ type Args = {
locale?: string
ref: Record<string, unknown>
textRows: Record<string, unknown>[]
withinArrayOrBlockLocale?: string
}
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)
}
export const transformHasManyText = ({ field, locale, ref, textRows }: Args) => {
const result = textRows.map(({ text }) => text)
if (locale) {
ref[field.name][locale] = result

View File

@@ -6,31 +6,21 @@ type Args = {
locale?: string
ref: Record<string, unknown>
relations: Record<string, unknown>[]
withinArrayOrBlockLocale?: string
}
export const transformRelationship = ({
field,
locale,
ref,
relations,
withinArrayOrBlockLocale,
}: Args) => {
export const transformRelationship = ({ field, locale, ref, relations }: Args) => {
let result: unknown
if (!('hasMany' in field) || field.hasMany === false) {
let relation = relations[0]
if (withinArrayOrBlockLocale) {
relation = relations.find((rel) => rel.locale === withinArrayOrBlockLocale)
}
const relation = relations[0]
if (relation) {
// Handle hasOne Poly
if (Array.isArray(field.relationTo)) {
const matchedRelation = Object.entries(relation).find(([key, val]) => {
return val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key)
})
const matchedRelation = Object.entries(relation).find(
([key, val]) =>
val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key),
)
if (matchedRelation) {
const relationTo = matchedRelation[0].replace('ID', '')
@@ -50,26 +40,18 @@ export const transformRelationship = ({
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 && matchedLocale) {
if (relatedData) {
transformedRelations.push(relatedData)
}
} else {
// Handle hasMany Poly
const matchedRelation = Object.entries(relation).find(
([key, val]) =>
val !== null &&
!['id', 'locale', 'order', 'parent', 'path'].includes(key) &&
matchedLocale,
val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key),
)
if (matchedRelation) {

View File

@@ -55,10 +55,6 @@ 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
@@ -75,7 +71,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
relationships,
table,
texts,
withinArrayOrBlockLocale,
}: TraverseFieldsArgs): T => {
const sanitizedPath = path ? `${path}.` : path
@@ -93,7 +88,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
relationships,
table,
texts,
withinArrayOrBlockLocale,
})
}
@@ -114,7 +108,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
relationships,
table,
texts,
withinArrayOrBlockLocale,
})
}
@@ -152,7 +145,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
relationships,
table: row,
texts,
withinArrayOrBlockLocale: locale,
})
if ('_order' in rowResult) {
@@ -165,7 +157,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
return arrayResult
}, {})
} else {
result[field.name] = fieldData.reduce((acc, row, i) => {
result[field.name] = fieldData.map((row, i) => {
if (row._uuid) {
row.id = row._uuid
delete row._uuid
@@ -175,48 +167,34 @@ export const traverseFields = <T extends Record<string, unknown>>({
delete row._order
}
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 traverseFields<T>({
blocks,
config,
dataRef: row,
deletions,
fieldPrefix: '',
fields: field.fields,
numbers,
path: `${sanitizedPath}${field.name}.${i}`,
relationships,
table: row,
texts,
})
})
}
}
return result
}
if (field.type === 'blocks') {
const blockFieldPath = `${sanitizedPath}${field.name}`
const blocksByPath = blocks[blockFieldPath]
if (Array.isArray(blocksByPath)) {
if (Array.isArray(blocks[blockFieldPath])) {
if (field.localized) {
result[field.name] = {}
blocksByPath.forEach((row) => {
blocks[blockFieldPath].forEach((row) => {
if (row._uuid) {
row.id = row._uuid
delete row._uuid
@@ -245,7 +223,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
relationships,
table: row,
texts,
withinArrayOrBlockLocale: locale,
})
delete blockResult._order
@@ -256,23 +233,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
})
})
} else {
// 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) => {
result[field.name] = blocks[blockFieldPath].map((row, i) => {
delete row._order
if (row._uuid) {
row.id = row._uuid
@@ -281,43 +242,23 @@ export const traverseFields = <T extends Record<string, unknown>>({
const block = field.blocks.find(({ slug }) => slug === row.blockType)
if (block) {
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 traverseFields<T>({
blocks,
config,
dataRef: row,
deletions,
fieldPrefix: '',
fields: block.fields,
numbers,
path: `${blockFieldPath}.${i}`,
relationships,
table: row,
texts,
})
}
return acc
}, [])
return {}
})
}
}
@@ -364,7 +305,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
field,
ref: result,
relations: relationPathMatch,
withinArrayOrBlockLocale,
})
}
@@ -399,7 +339,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
field,
ref: result,
textRows: textPathMatch,
withinArrayOrBlockLocale,
})
}
@@ -434,7 +373,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
field,
numberRows: numberPathMatch,
ref: result,
withinArrayOrBlockLocale,
})
}
@@ -453,11 +391,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
return selectResult
}, {})
} else {
let selectData = fieldData
if (withinArrayOrBlockLocale) {
selectData = selectData.filter(({ locale }) => locale === withinArrayOrBlockLocale)
}
result[field.name] = selectData.map(({ value }) => value)
result[field.name] = fieldData.map(({ value }) => value)
}
}
return result
@@ -470,20 +404,8 @@ 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: {
...table,
...localeRow,
},
})
valuesToTransform.push({ ref: localizedFieldData, table: localeRow })
})
} else {
valuesToTransform.push({ ref: result, table })
@@ -497,28 +419,50 @@ 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 && 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,
})
if (field.localized) {
if (typeof locale === 'string' && !ref[locale]) {
ref[locale] = {}
delete table._locale
}
if ('_order' in ref) {
delete ref._order
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
}
}
break

View File

@@ -26,11 +26,6 @@ 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 = ({
@@ -48,7 +43,6 @@ export const transformArray = ({
relationshipsToDelete,
selects,
texts,
withinArrayOrBlockLocale,
}: Args) => {
const newRows: ArrayRowToInsert[] = []
@@ -84,10 +78,6 @@ export const transformArray = ({
newRow.row._locale = locale
}
if (withinArrayOrBlockLocale) {
newRow.row._locale = withinArrayOrBlockLocale
}
traverseFields({
adapter,
arrays: newRow.arrays,
@@ -107,7 +97,6 @@ export const transformArray = ({
row: newRow.row,
selects,
texts,
withinArrayOrBlockLocale,
})
newRows.push(newRow)

View File

@@ -26,11 +26,6 @@ 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,
@@ -46,7 +41,6 @@ export const transformBlocks = ({
relationshipsToDelete,
selects,
texts,
withinArrayOrBlockLocale,
}: Args) => {
data.forEach((blockRow, i) => {
if (typeof blockRow.blockType !== 'string') return
@@ -66,7 +60,6 @@ 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}`)
@@ -101,7 +94,6 @@ export const transformBlocks = ({
row: newRow.row,
selects,
texts,
withinArrayOrBlockLocale,
})
blocks[blockType].push(newRow)

View File

@@ -58,11 +58,6 @@ 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 = ({
@@ -86,7 +81,6 @@ export const traverseFields = ({
row,
selects,
texts,
withinArrayOrBlockLocale,
}: Args) => {
fields.forEach((field) => {
let columnName = ''
@@ -123,7 +117,6 @@ export const traverseFields = ({
relationshipsToDelete,
selects,
texts,
withinArrayOrBlockLocale: localeKey,
})
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
@@ -145,7 +138,6 @@ export const traverseFields = ({
relationshipsToDelete,
selects,
texts,
withinArrayOrBlockLocale,
})
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
@@ -177,7 +169,6 @@ export const traverseFields = ({
relationshipsToDelete,
selects,
texts,
withinArrayOrBlockLocale: localeKey,
})
}
})
@@ -196,7 +187,6 @@ export const traverseFields = ({
relationshipsToDelete,
selects,
texts,
withinArrayOrBlockLocale,
})
}
@@ -207,9 +197,6 @@ 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,
@@ -231,14 +218,9 @@ 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,
@@ -246,7 +228,7 @@ export const traverseFields = ({
blocks,
blocksToDelete,
columnPrefix: `${columnName}_`,
data: groupData,
data: data[field.name] as Record<string, unknown>,
existingLocales,
fieldPrefix: `${fieldName}_`,
fields: field.fields,
@@ -259,7 +241,6 @@ export const traverseFields = ({
row,
selects,
texts,
withinArrayOrBlockLocale,
})
}
}
@@ -273,9 +254,6 @@ 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,
@@ -297,14 +275,9 @@ 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,
@@ -312,7 +285,7 @@ export const traverseFields = ({
blocks,
blocksToDelete,
columnPrefix: `${columnPrefix || ''}${toSnakeCase(tab.name)}_`,
data: tabData,
data: data[tab.name] as Record<string, unknown>,
existingLocales,
fieldPrefix: `${fieldPrefix || ''}${tab.name}_`,
fields: tab.fields,
@@ -325,7 +298,6 @@ export const traverseFields = ({
row,
selects,
texts,
withinArrayOrBlockLocale,
})
}
}
@@ -350,7 +322,6 @@ export const traverseFields = ({
row,
selects,
texts,
withinArrayOrBlockLocale,
})
}
})
@@ -368,7 +339,6 @@ export const traverseFields = ({
existingLocales,
fieldPrefix,
fields: field.fields,
forcedLocale,
locales,
numbers,
parentTableName,
@@ -378,7 +348,6 @@ export const traverseFields = ({
row,
selects,
texts,
withinArrayOrBlockLocale,
})
}
@@ -415,7 +384,6 @@ export const traverseFields = ({
transformRelationship({
baseRow: {
locale: withinArrayOrBlockLocale,
path: relationshipPath,
},
data: fieldData,
@@ -448,7 +416,6 @@ export const traverseFields = ({
} else if (Array.isArray(fieldData)) {
transformTexts({
baseRow: {
locale: withinArrayOrBlockLocale,
path: textPath,
},
data: fieldData,
@@ -480,7 +447,6 @@ export const traverseFields = ({
} else if (Array.isArray(fieldData)) {
transformNumbers({
baseRow: {
locale: withinArrayOrBlockLocale,
path: numberPath,
},
data: fieldData,
@@ -513,7 +479,6 @@ export const traverseFields = ({
const newRows = transformSelects({
id: data._uuid || data.id,
data: data[field.name],
locale: withinArrayOrBlockLocale,
})
selects[selectTableName] = selects[selectTableName].concat(newRows)

View File

@@ -13,8 +13,8 @@ import type {
PgSchema,
PgTableWithColumns,
PgTransaction,
pgEnum,
} 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'
@@ -79,7 +79,7 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
idType: Args['idType']
localesSuffix?: string
logger: DrizzleConfig['logger']
pgSchema?: Schema
pgSchema: Schema
pool: Pool
poolOptions: Args['pool']
push: boolean

View File

@@ -54,10 +54,7 @@ export const insertArrays = async ({ adapter, arrays, db, parentRows }: Args): P
arrayRowLocaleData._locale = arrayRowLocale
rowsByTable[tableName].locales.push(arrayRowLocaleData)
if (!arrayRow.row.id) {
arrayRowLocaleData._getParentID = (rows: { _uuid: string; id: number }[]) => {
const { id } = rows.find((each) => each._uuid === arrayRow.row._uuid)
return id
}
arrayRowLocaleData._getParentID = (rows) => rows[i].id
}
})
})

View File

@@ -4,10 +4,8 @@ import { fieldAffectsData, fieldHasSubFields } from 'payload/types'
export const hasLocalesTable = (fields: Field[]): boolean => {
return fields.some((field) => {
// arrays always get a separate table
if (field.type === 'array') return false
if (fieldAffectsData(field) && field.localized) return true
if (fieldHasSubFields(field)) return hasLocalesTable(field.fields)
if (fieldHasSubFields(field) && field.type !== 'array') return hasLocalesTable(field.fields)
if (field.type === 'tabs') return field.tabs.some((tab) => hasLocalesTable(tab.fields))
return false
})

View File

@@ -1,18 +1,11 @@
import { sql } from 'drizzle-orm'
import type { PostgresAdapter } from '../types.js'
import type { DrizzleDB } from '../types'
export const migrationTableExists = async (adapter: PostgresAdapter): Promise<boolean> => {
let statement
export const migrationTableExists = async (db: DrizzleDB): Promise<boolean> => {
const queryRes = await db.execute(sql`SELECT to_regclass('public.payload_migrations');`)
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
// Returns table name 'payload_migrations' or null
const exists = queryRes.rows?.[0]?.to_regclass === 'payload_migrations'
return exists
}

View File

@@ -1,76 +0,0 @@
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'`),
)
}
}

View File

@@ -1,12 +0,0 @@
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')

View File

@@ -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.2",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-regexp": "1.15.0"
},
"keywords": []

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.30.3",
"version": "2.25.0",
"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.2",
"@faceless-ui/modal": "2.0.1",
"@faceless-ui/scroll-info": "1.3.0",
"@faceless-ui/window-info": "2.1.2",
"@faceless-ui/window-info": "2.1.1",
"@monaco-editor/react": "4.5.1",
"@swc/core": "1.6.1",
"@swc/register": "0.1.10",
"body-parser": "1.20.3",
"body-parser": "1.20.2",
"body-scroll-lock": "4.0.0-beta.0",
"bson-objectid": "2.0.4",
"compression": "1.7.4",
@@ -70,10 +70,10 @@
"console-table-printer": "2.11.2",
"dataloader": "2.2.2",
"date-fns": "2.30.0",
"deep-equal": "2.2.3",
"deep-equal": "2.2.2",
"deepmerge": "4.3.1",
"dotenv": "8.6.0",
"express": "4.21.0",
"express": "4.18.2",
"express-fileupload": "1.4.0",
"express-rate-limit": "5.5.1",
"file-type": "16.5.4",
@@ -96,15 +96,15 @@
"is-plain-object": "5.0.0",
"isomorphic-fetch": "3.0.0",
"joi": "17.9.2",
"json-schema-to-typescript": "14.0.5",
"jsonwebtoken": "9.0.2",
"json-schema-to-typescript": "11.0.3",
"jsonwebtoken": "9.0.1",
"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.15",
"nodemailer": "6.9.8",
"object-to-formdata": "4.5.1",
"passport": "0.6.0",
"passport-anonymous": "1.0.1",
@@ -112,7 +112,7 @@
"passport-jwt": "4.0.1",
"passport-local": "1.0.0",
"pino": "8.15.0",
"pino-pretty": "10.3.1",
"pino-pretty": "10.2.0",
"pluralize": "8.0.0",
"probe-image-size": "6.0.0",
"process": "0.11.10",
@@ -129,14 +129,14 @@
"react-router-dom": "5.3.4",
"react-router-navigation-prompt": "1.9.6",
"react-select": "5.7.4",
"react-toastify": "10.0.5",
"react-toastify": "8.2.0",
"sanitize-filename": "1.6.3",
"sass": "1.69.4",
"scheduler": "0.23.2",
"scheduler": "0.23.0",
"scmp": "2.1.0",
"sharp": "0.32.6",
"swc-loader": "0.2.6",
"terser-webpack-plugin": "5.3.10",
"swc-loader": "0.2.3",
"terser-webpack-plugin": "5.3.9",
"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.5",
"@types/body-parser": "1.19.2",
"@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": "9.0.7",
"@types/jsonwebtoken": "8.5.9",
"@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.16",
"@types/nodemailer": "6.4.14",
"@types/passport": "1.0.12",
"@types/passport-anonymous": "1.0.3",
"@types/passport-jwt": "3.0.9",
@@ -199,12 +199,12 @@
"postcss-loader": "6.2.1",
"postcss-preset-env": "9.0.0",
"release-it": "16.1.3",
"rimraf": "4.4.1",
"rimraf": "3.0.2",
"sass-loader": "12.6.0",
"serve-static": "1.15.0",
"swc-loader": "^0.2.6",
"swc-loader": "^0.2.3",
"terser": "5.19.2",
"terser-webpack-plugin": "^5.3.10",
"terser-webpack-plugin": "^5.3.6",
"url-loader": "4.1.1",
"vite": "^4.4.9",
"webpack": "^5.78.0"

View File

@@ -18,7 +18,7 @@ import './index.scss'
const baseClass = 'delete-documents'
const DeleteMany: React.FC<Props> = (props) => {
const { collection: { slug, labels: { plural } } = {}, resetParams } = props
const { collection: { labels: { plural }, slug } = {}, resetParams } = props
const { permissions } = useAuth()
const {
@@ -41,7 +41,7 @@ const DeleteMany: React.FC<Props> = (props) => {
const handleDelete = useCallback(() => {
setDeleting(true)
void requests
requests
.delete(`${serverURL}${api}/${slug}${getQueryParams()}`, {
headers: {
'Accept-Language': i18n.language,
@@ -60,15 +60,7 @@ const DeleteMany: React.FC<Props> = (props) => {
}
if (json.errors) {
let message = json.message
if (json.errors) {
json.errors.forEach((error) => {
message = message + '\n' + error.message
})
}
toast.error(message)
toast.error(json.message)
} else {
addDefaultError()
}

View File

@@ -1,70 +0,0 @@
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
}
})
}

View File

@@ -13,7 +13,6 @@ 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'
@@ -66,8 +65,6 @@ 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,
@@ -76,8 +73,6 @@ 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

View File

@@ -22,7 +22,6 @@
padding-top: base(0.25);
padding-bottom: base(0.25);
padding-left: base(0.25);
padding-right: base(0.25);
.rs__multi-value {
margin: calc(#{base(0.125)} - #{$style-stroke-width-s * 2});

View File

@@ -10,12 +10,6 @@
}
}
.has-many {
.rs__input-container {
overflow: hidden;
}
}
html[data-theme='light'] {
.field-type.text {
&.error {

View File

@@ -36,7 +36,6 @@ const useField = <T,>(options: Options): FieldType<T> => {
const showError = valid === false && submitted
const prevValid = useRef(valid)
const prevErrorMessage = useRef(field?.errorMessage)
const prevValue = useRef(value)
// Method to return from `useField`, used to
@@ -129,9 +128,8 @@ const useField = <T,>(options: Options): FieldType<T> => {
// Only dispatch if the validation result has changed
// This will prevent unnecessary rerenders
if (valid !== prevValid.current || errorMessage !== prevErrorMessage.current) {
if (valid !== prevValid.current) {
prevValid.current = valid
prevErrorMessage.current = errorMessage
if (typeof dispatchField === 'function') {
dispatchField({

View File

@@ -65,7 +65,7 @@ const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
}, [global.slug, location.pathname, global?.admin?.components?.views?.Edit, setViewActions])
return (
<main className={`${baseClass} ${baseClass}--${global.slug}`}>
<main className={baseClass}>
<OperationContext.Provider value="update">
<SetStepNav global={global} />
<Form

View File

@@ -25,13 +25,7 @@ const generateLabelFromValue = (
locale: string,
value: { relationTo: string; value: RelationshipValue } | RelationshipValue,
): string => {
if (Array.isArray(value)) {
return value
.map((v) => generateLabelFromValue(collections, field, locale, v))
.filter(Boolean) // Filters out any undefined or empty values
.join(', ')
}
let relation: string
let relatedDoc: RelationshipValue
let valueToReturn = '' as any
@@ -39,58 +33,38 @@ const generateLabelFromValue = (
return String(value)
}
const relationTo = 'relationTo' in field ? field.relationTo : undefined
if (value === null || typeof value === 'undefined') {
return String(value)
}
if (typeof value === 'object' && 'relationTo' in value) {
relatedDoc = value.value
if (Array.isArray(field.relationTo)) {
if (typeof value === 'object') {
relation = value.relationTo
relatedDoc = value.value
}
} else {
// Non-polymorphic relationship
relation = field.relationTo
relatedDoc = value
}
const relatedCollection = relationTo
? collections.find(
(c) =>
c.slug ===
(typeof value === 'object' && 'relationTo' in value ? value.relationTo : relationTo),
)
: null
const relatedCollection = collections.find((c) => c.slug === relation)
if (relatedCollection) {
const useAsTitle = relatedCollection?.admin?.useAsTitle
// eslint-disable-next-line react-hooks/rules-of-hooks
const useAsTitleField = useUseTitleField(relatedCollection)
let titleFieldIsLocalized = false
if (useAsTitleField && fieldAffectsData(useAsTitleField)) {
if (useAsTitleField && fieldAffectsData(useAsTitleField))
titleFieldIsLocalized = useAsTitleField.localized
}
if (typeof relatedDoc?.[useAsTitle] !== 'undefined') {
valueToReturn = relatedDoc[useAsTitle]
} else if (typeof relatedDoc?.id !== 'undefined') {
valueToReturn = relatedDoc.id
} else {
valueToReturn = relatedDoc
}
if (typeof valueToReturn === 'object' && titleFieldIsLocalized) {
valueToReturn = valueToReturn[locale]
}
} else if (relatedDoc) {
// Handle non-polymorphic `hasMany` relationships or fallback
if (typeof relatedDoc.id !== 'undefined') {
valueToReturn = relatedDoc.id
} else {
valueToReturn = relatedDoc
}
}
if (typeof valueToReturn === 'object' && valueToReturn !== null) {
valueToReturn = JSON.stringify(valueToReturn)
}
return valueToReturn
@@ -105,31 +79,25 @@ const Relationship: React.FC<Props & { field: RelationshipField }> = ({
const { i18n, t } = useTranslation('general')
const { code: locale } = useLocale()
const placeholder = `[${t('noValue')}]`
let placeholder = ''
let versionToRender: string | undefined = placeholder
let comparisonToRender: string | undefined = placeholder
if (version === comparison) placeholder = `[${t('noValue')}]`
if (version) {
if ('hasMany' in field && field.hasMany && Array.isArray(version)) {
versionToRender =
version.map((val) => generateLabelFromValue(collections, field, locale, val)).join(', ') ||
placeholder
} else {
versionToRender = generateLabelFromValue(collections, field, locale, version) || placeholder
}
}
let versionToRender = version
let comparisonToRender = comparison
if (comparison) {
if ('hasMany' in field && field.hasMany && Array.isArray(comparison)) {
comparisonToRender =
comparison
.map((val) => generateLabelFromValue(collections, field, locale, val))
.join(', ') || placeholder
} else {
comparisonToRender =
generateLabelFromValue(collections, field, locale, comparison) || placeholder
}
if (field.hasMany) {
if (Array.isArray(version))
versionToRender = version
.map((val) => generateLabelFromValue(collections, field, locale, val))
.join(', ')
if (Array.isArray(comparison))
comparisonToRender = comparison
.map((val) => generateLabelFromValue(collections, field, locale, val))
.join(', ')
} else {
versionToRender = generateLabelFromValue(collections, field, locale, version)
comparisonToRender = generateLabelFromValue(collections, field, locale, comparison)
}
return (

View File

@@ -108,8 +108,6 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
initialParams: { depth: 1, draft: 'true', locale: '*' },
})
const hasDraftsEnabled = collection?.versions?.drafts || global?.versions?.drafts
const sharedParams = (status) => {
return {
depth: 0,
@@ -124,26 +122,24 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
}
const [{ data: draft }] = usePayloadAPI(compareBaseURL, {
initialParams: hasDraftsEnabled ? { ...sharedParams('draft') } : {},
initialParams: { ...sharedParams('draft') },
})
const [{ data: published }] = usePayloadAPI(compareBaseURL, {
initialParams: hasDraftsEnabled ? { ...sharedParams('published') } : {},
initialParams: { ...sharedParams('published') },
})
useEffect(() => {
if (hasDraftsEnabled) {
const formattedPublished = published?.docs?.length > 0 && published?.docs[0]
const formattedDraft = draft?.docs?.length > 0 && draft?.docs[0]
const formattedPublished = published?.docs?.length > 0 && published?.docs[0]
const formattedDraft = draft?.docs?.length > 0 && draft?.docs[0]
if (!formattedPublished || !formattedDraft) return
if (!formattedPublished || !formattedDraft) return
const publishedNewerThanDraft = formattedPublished?.updatedAt > formattedDraft?.updatedAt
const publishedNewerThanDraft = formattedPublished?.updatedAt > formattedDraft?.updatedAt
setLatestDraftVersion(publishedNewerThanDraft ? undefined : formattedDraft?.id)
setLatestPublishedVersion(formattedPublished.latest ? formattedPublished?.id : undefined)
}
}, [hasDraftsEnabled, draft, published])
setLatestDraftVersion(publishedNewerThanDraft ? undefined : formattedDraft?.id)
setLatestPublishedVersion(formattedPublished.latest ? formattedPublished?.id : undefined)
}, [draft, published])
useEffect(() => {
let nav: StepNavItem[] = []

View File

@@ -47,57 +47,45 @@ export const buildVersionColumns = (
t: TFunction,
latestDraftVersion?: string,
latestPublishedVersion?: string,
): Column[] => {
const entityConfig = collection || global
const columns: Column[] = [
{
name: '',
accessor: 'updatedAt',
active: true,
components: {
Heading: <SortColumn label={t('general:updatedAt')} name="updatedAt" />,
renderCell: (row, data) => (
<CreatedAtCell collection={collection} date={data} global={global} id={row?.id} />
),
},
label: '',
): Column[] => [
{
name: '',
accessor: 'updatedAt',
active: true,
components: {
Heading: <SortColumn label={t('general:updatedAt')} name="updatedAt" />,
renderCell: (row, data) => (
<CreatedAtCell collection={collection} date={data} global={global} id={row?.id} />
),
},
{
name: '',
accessor: 'id',
active: true,
components: {
Heading: <SortColumn disable label={t('versionID')} name="id" />,
renderCell: (row, data) => <TextCell>{data}</TextCell>,
},
label: '',
label: '',
},
{
name: '',
accessor: 'id',
active: true,
components: {
Heading: <SortColumn disable label={t('versionID')} name="id" />,
renderCell: (row, data) => <TextCell>{data}</TextCell>,
},
]
if (
entityConfig?.versions?.drafts ||
(entityConfig?.versions?.drafts && entityConfig.versions.drafts?.autosave)
) {
columns.push({
name: '',
accessor: 'autosave',
active: true,
components: {
Heading: <SortColumn disable label={t('status')} name="autosave" />,
renderCell: (row) => {
return (
<AutosaveCell
latestDraftVersion={latestDraftVersion}
latestPublishedVersion={latestPublishedVersion}
rowData={row}
/>
)
},
label: '',
},
{
name: '',
accessor: 'autosave',
active: true,
components: {
Heading: <SortColumn disable label={t('status')} name="autosave" />,
renderCell: (row) => {
return (
<AutosaveCell
latestDraftVersion={latestDraftVersion}
latestPublishedVersion={latestPublishedVersion}
rowData={row}
/>
)
},
label: '',
})
}
return columns
}
},
label: '',
},
]

View File

@@ -94,8 +94,6 @@ const VersionsView: React.FC<IndexProps> = (props) => {
const [{ data: versionsData, isLoading: isLoadingVersions }, { setParams }] =
usePayloadAPI(fetchURL)
const hasDraftsEnabled = collection?.versions?.drafts || global?.versions?.drafts
const sharedParams = (status) => {
return {
depth: 0,
@@ -110,25 +108,23 @@ const VersionsView: React.FC<IndexProps> = (props) => {
}
const [{ data: draft }] = usePayloadAPI(fetchURL, {
initialParams: hasDraftsEnabled ? { ...sharedParams('draft') } : {},
initialParams: { ...sharedParams('draft') },
})
const [{ data: published }] = usePayloadAPI(fetchURL, {
initialParams: hasDraftsEnabled ? { ...sharedParams('published') } : {},
initialParams: { ...sharedParams('published') },
})
useEffect(() => {
if (hasDraftsEnabled) {
const formattedPublished = published?.docs?.length > 0 && published?.docs[0]
const formattedDraft = draft?.docs?.length > 0 && draft?.docs[0]
const formattedPublished = published?.docs?.length > 0 && published?.docs[0]
const formattedDraft = draft?.docs?.length > 0 && draft?.docs[0]
if (!formattedPublished || !formattedDraft) return
if (!formattedPublished || !formattedDraft) return
const publishedNewerThanDraft = formattedPublished?.updatedAt > formattedDraft?.updatedAt
setLatestDraftVersion(publishedNewerThanDraft ? undefined : formattedDraft?.id)
setLatestPublishedVersion(formattedPublished.latest ? formattedPublished?.id : undefined)
}
}, [hasDraftsEnabled, draft, published])
const publishedNewerThanDraft = formattedPublished?.updatedAt > formattedDraft?.updatedAt
setLatestDraftVersion(publishedNewerThanDraft ? undefined : formattedDraft?.id)
setLatestPublishedVersion(formattedPublished.latest ? formattedPublished?.id : undefined)
}, [draft, published])
useEffect(() => {
const params = {

View File

@@ -50,13 +50,7 @@ const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => {
const { auth } = collection
const classes = [
baseClass,
`${baseClass}--${collection.slug}`,
isEditing && `${baseClass}--is-editing`,
]
.filter(Boolean)
.join(' ')
const classes = [baseClass, isEditing && `${baseClass}--is-editing`].filter(Boolean).join(' ')
const location = useLocation()

View File

@@ -12,11 +12,7 @@ const ArrayCell: React.FC<CellComponentProps<ArrayField, Record<string, unknown>
}) => {
const { i18n, t } = useTranslation('general')
const arrayFields = data ?? []
const label =
arrayFields.length === 1
? `${arrayFields.length} ${getTranslation(field?.labels?.singular || t('row'), i18n)}`
: `${arrayFields.length} ${getTranslation(field?.labels?.plural || t('rows'), i18n)}`
const label = `${arrayFields.length} ${getTranslation(field?.labels?.plural || t('rows'), i18n)}`
return <span>{label}</span>
}

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { RelationshipField, UploadField } from '../../../../../../../../exports/types'
import type { RelationshipField } from '../../../../../../../../exports/types'
import type { CellComponentProps } from '../../types'
import { getTranslation } from '../../../../../../../../utilities/getTranslation'
@@ -9,14 +9,13 @@ import useIntersect from '../../../../../../../hooks/useIntersect'
import { formatUseAsTitle } from '../../../../../../../hooks/useTitle'
import { useConfig } from '../../../../../../utilities/Config'
import { useListRelationships } from '../../../RelationshipProvider'
import File from '../File'
import './index.scss'
type Value = { relationTo: string; value: number | string }
const baseClass = 'relationship-cell'
const totalToShow = 3
const RelationshipCell: React.FC<CellComponentProps<RelationshipField | UploadField>> = (props) => {
const RelationshipCell: React.FC<CellComponentProps<RelationshipField>> = (props) => {
const { data: cellData, field } = props
const config = useConfig()
const { collections, routes } = config
@@ -69,24 +68,11 @@ const RelationshipCell: React.FC<CellComponentProps<RelationshipField | UploadFi
i18n,
})
let fileField = null
if (field.type === 'upload') {
const relatedCollectionPreview = !!relatedCollection.upload.displayPreview
const fieldPreview = field.displayPreview
const previewAllowed =
fieldPreview || (relatedCollectionPreview && fieldPreview !== false)
if (previewAllowed && document) {
fileField = (
<File collection={relatedCollection} data={label} field={field} rowData={document} />
)
}
}
return (
<React.Fragment key={i}>
{document === false && `${t('untitled')} - ID: ${value}`}
{document === null && `${t('loading')}...`}
{document && (fileField || label || `${t('untitled')} - ID: ${value}`)}
{document && (label || `${t('untitled')} - ID: ${value}`)}
{values.length > i + 1 && ', '}
</React.Fragment>
)

View File

@@ -68,7 +68,7 @@ const DefaultList: React.FC<Props> = (props) => {
}
return (
<div className={`${baseClass} ${baseClass}--${collection.slug}`}>
<div className={baseClass}>
{Array.isArray(BeforeList) &&
BeforeList.map((Component, i) => <Component key={i} {...props} />)}

View File

@@ -41,7 +41,7 @@ export const registerLocalStrategy = async ({
const sanitizedDoc = { ...doc }
if (sanitizedDoc.password) delete sanitizedDoc.password
const dbArgs = {
return payload.db.create({
collection: collection.slug,
data: {
...sanitizedDoc,
@@ -49,10 +49,5 @@ export const registerLocalStrategy = async ({
salt,
},
req,
}
if (collection?.db?.create) {
return collection.db.create(dbArgs)
} else {
return payload.db.create(dbArgs)
}
})
}

View File

@@ -118,7 +118,6 @@ const collectionSchema = joi.object().keys({
joi.boolean(),
),
custom: joi.object().pattern(joi.string(), joi.any()),
db: joi.object(),
dbName: joi.alternatives().try(joi.string(), joi.func()),
defaultSort: joi.string(),
endpoints: endpointsSchema,
@@ -172,7 +171,6 @@ const collectionSchema = joi.object().keys({
adminThumbnail: joi.alternatives().try(joi.string(), joi.func()),
crop: joi.bool(),
disableLocalStorage: joi.bool(),
displayPreview: joi.bool().default(false),
externalFileHeaderFilter: joi.func(),
filesRequiredOnCreate: joi.bool(),
focalPoint: joi.bool(),

View File

@@ -3,7 +3,7 @@ import type { Response } from 'express'
import type { GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from 'graphql'
import type { DeepRequired } from 'ts-essentials'
import type { DatabaseAdapter, GeneratedTypes } from '../../'
import type { GeneratedTypes } from '../../'
import type {
CustomPreviewButtonProps,
CustomPublishButtonType,
@@ -383,14 +383,6 @@ export type CollectionConfig = {
auth?: IncomingAuthType | boolean
/** Extension point to add your custom data. */
custom?: Record<string, any>
/**
* Add a custom database adapter to this collection.
*/
db?: Pick<
DatabaseAdapter,
'create' | 'deleteMany' | 'deleteOne' | 'find' | 'findOne' | 'updateOne'
>
/**
* Used to override the default naming of the database table or collection with your using a function or string
* @WARNING: If you change this property with existing data, you will need to handle the renaming of the table in your database or by using migrations

View File

@@ -242,16 +242,11 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
req,
})
} else {
const dbArgs = {
doc = await payload.db.create({
collection: collectionConfig.slug,
data: resultWithLocales,
req,
}
if (collectionConfig?.db?.create) {
doc = await collectionConfig.db.create(dbArgs)
} else {
doc = await payload.db.create(dbArgs)
}
})
}
const verificationToken = doc._verificationToken

View File

@@ -104,20 +104,12 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
// Retrieve documents
// /////////////////////////////////////
const dbArgs = {
const { docs } = await payload.db.find<GeneratedTypes['collections'][TSlug]>({
collection: collectionConfig.slug,
locale,
req,
where: fullWhere,
}
let docs
if (collectionConfig?.db?.find) {
const result = await collectionConfig.db.find<GeneratedTypes['collections'][TSlug]>(dbArgs)
docs = result.docs
} else {
const result = await payload.db.find<GeneratedTypes['collections'][TSlug]>(dbArgs)
docs = result.docs
}
})
const errors = []
@@ -168,7 +160,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
// Delete document
// /////////////////////////////////////
const deleteOneArgs = {
await payload.db.deleteOne({
collection: collectionConfig.slug,
req,
where: {
@@ -176,12 +168,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
equals: id,
},
},
}
if (collectionConfig?.db?.deleteOne) {
await collectionConfig.db.deleteOne(deleteOneArgs)
} else {
await payload.db.deleteOne(deleteOneArgs)
}
})
// /////////////////////////////////////
// afterRead - Fields

View File

@@ -96,19 +96,13 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
// /////////////////////////////////////
// Retrieve document
// /////////////////////////////////////
let docToDelete: Document
const dbArgs = {
const docToDelete = await req.payload.db.findOne({
collection: collectionConfig.slug,
locale: req.locale,
req,
where: combineQueries({ id: { equals: id } }, accessResults),
}
if (collectionConfig?.db?.findOne) {
docToDelete = await collectionConfig.db.findOne(dbArgs)
} else {
docToDelete = await req.payload.db.findOne(dbArgs)
}
})
if (!docToDelete && !hasWhereAccess) throw new NotFound(t)
if (!docToDelete && hasWhereAccess) throw new Forbidden(t)
@@ -138,17 +132,11 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
// Delete document
// /////////////////////////////////////
let result
const deleteOneArgs = {
let result = await req.payload.db.deleteOne({
collection: collectionConfig.slug,
req,
where: { id: { equals: id } },
}
if (collectionConfig?.db?.deleteOne) {
result = await collectionConfig?.db.deleteOne(deleteOneArgs)
} else {
result = await payload.db.deleteOne(deleteOneArgs)
}
})
// /////////////////////////////////////
// Delete Preferences

View File

@@ -142,7 +142,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
where,
})
const dbArgs = {
result = await payload.db.find<T>({
collection: collectionConfig.slug,
limit: sanitizedLimit,
locale,
@@ -151,13 +151,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
req,
sort,
where: fullWhere,
}
if (collectionConfig?.db?.find) {
result = await collectionConfig.db.find<T>(dbArgs)
} else {
result = await payload.db.find<T>(dbArgs)
}
})
}
// /////////////////////////////////////

View File

@@ -87,12 +87,7 @@ async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<
if (!findOneArgs.where.and[0].id) throw new NotFound(t)
let result: T
if (collectionConfig?.db?.findOne) {
result = await collectionConfig.db.findOne(findOneArgs)
} else {
result = await req.payload.db.findOne(findOneArgs)
}
let result: T = await req.payload.db.findOne(findOneArgs)
if (!result) {
if (!disableErrors) {

View File

@@ -85,12 +85,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
where: combineQueries({ id: { equals: parentDocID } }, accessResults),
}
let doc: T
if (collectionConfig?.db?.findOne) {
doc = await collectionConfig.db.findOne(findOneArgs)
} else {
doc = await req.payload.db.findOne(findOneArgs)
}
const doc = await req.payload.db.findOne(findOneArgs)
if (!doc && !hasWherePolicy) throw new NotFound(t)
if (!doc && hasWherePolicy) throw new Forbidden(t)
@@ -111,18 +106,12 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
// Update
// /////////////////////////////////////
const restoreVersionArgs = {
let result = await req.payload.db.updateOne({
id: parentDocID,
collection: collectionConfig.slug,
data: rawVersion.version,
req,
}
let result
if (collectionConfig?.db?.updateOne) {
result = await collectionConfig.db.updateOne(restoreVersionArgs)
} else {
result = await req.payload.db.updateOne(restoreVersionArgs)
}
})
// /////////////////////////////////////
// Save `previousDoc` as a version after restoring

View File

@@ -137,21 +137,14 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
docs = query.docs
} else {
const dbArgs = {
const query = await payload.db.find({
collection: collectionConfig.slug,
limit: 0,
locale,
pagination: false,
req,
where: fullWhere,
}
let query
if (collectionConfig?.db?.find) {
query = await collectionConfig.db.find(dbArgs)
} else {
query = await payload.db.find(dbArgs)
}
})
docs = query.docs
}
@@ -289,18 +282,13 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
// /////////////////////////////////////
if (!shouldSaveDraft || data._status === 'published') {
const dbArgs = {
result = await req.payload.db.updateOne({
id,
collection: collectionConfig.slug,
data: result,
locale,
req,
}
if (collectionConfig?.db?.updateOne) {
result = await collectionConfig.db.updateOne(dbArgs)
} else {
result = await req.payload.db.updateOne(dbArgs)
}
})
}
// /////////////////////////////////////

View File

@@ -91,9 +91,9 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
}
let { data } = args
const dataHasPassword = 'password' in data && data.password
const { password } = data
const shouldSaveDraft = Boolean(draftArg && collectionConfig.versions.drafts)
const shouldSavePassword = Boolean(dataHasPassword && collectionConfig.auth && !shouldSaveDraft)
const shouldSavePassword = Boolean(password && collectionConfig.auth && !shouldSaveDraft)
// /////////////////////////////////////
// Access
@@ -256,7 +256,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
// /////////////////////////////////////
const dataToUpdate: Record<string, unknown> = { ...result }
const { password } = dataToUpdate
if (shouldSavePassword && typeof password === 'string') {
const { hash, salt } = await generatePasswordSaltHash({ password })
dataToUpdate.salt = salt
@@ -270,18 +270,13 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
// /////////////////////////////////////
if (!shouldSaveDraft || data._status === 'published') {
const dbArgs = {
result = await req.payload.db.updateOne({
id,
collection: collectionConfig.slug,
data: dataToUpdate,
locale,
req,
}
if (collectionConfig?.db?.updateOne) {
result = await collectionConfig.db.updateOne(dbArgs)
} else {
result = await req.payload.db.updateOne(dbArgs)
}
})
}
// /////////////////////////////////////

Some files were not shown because too many files have changed in this diff Show More