Compare commits
1 Commits
v3.0.0-bet
...
v3.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9c7f9da92 |
38
.github/CODEOWNERS
vendored
38
.github/CODEOWNERS
vendored
@@ -1,33 +1,41 @@
|
||||
# Order matters. The last matching pattern takes precedence.
|
||||
|
||||
### Package Exports ###
|
||||
/**/exports/ @denolfe @jmikrut
|
||||
### Core ###
|
||||
/packages/payload/src/uploads/ @denolfe
|
||||
/packages/payload/src/admin/ @jmikrut @jacobsfletch @JarrodMFlesch
|
||||
|
||||
### Adapters ###
|
||||
/packages/richtext-*/ @AlessioGr
|
||||
/packages/db-*/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/richtext-*/ @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
|
||||
### Plugins ###
|
||||
/packages/plugin-*/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/plugin-cloud*/ @denolfe
|
||||
/packages/plugin-form-builder/ @jacobsfletch
|
||||
/packages/plugin-live-preview*/ @jacobsfletch
|
||||
/packages/plugin-nested-docs/ @jacobsfletch
|
||||
/packages/plugin-redirects/ @jacobsfletch
|
||||
/packages/plugin-search/ @jacobsfletch
|
||||
/packages/plugin-sentry/ @JessChowdhury
|
||||
/packages/plugin-seo/ @jacobsfletch
|
||||
/packages/plugin-stripe/ @jacobsfletch
|
||||
|
||||
### Examples ###
|
||||
/examples/ @jacobsfletch
|
||||
/examples/testing/ @JarrodMFlesch
|
||||
/examples/email/ @JessChowdhury
|
||||
/examples/whitelabel/ @JessChowdhury
|
||||
|
||||
### Templates ###
|
||||
/templates/ @jacobsfletch @denolfe
|
||||
|
||||
### Misc ###
|
||||
/packages/create-payload-app/ @denolfe
|
||||
/packages/eslint-*/ @denolfe
|
||||
|
||||
### Build Files ###
|
||||
/**/package.json @denolfe
|
||||
|
||||
/tsconfig.json @denolfe
|
||||
/**/tsconfig*.json @denolfe
|
||||
|
||||
/jest.config.js @denolfe
|
||||
/**/jest.config.js @denolfe
|
||||
/packages/eslint-config-payload/ @denolfe
|
||||
/packages/payload-admin-bar/ @jacobsfletch
|
||||
|
||||
### Root ###
|
||||
/package.json @denolfe
|
||||
/scripts/ @denolfe
|
||||
/.husky/ @denolfe
|
||||
/.vscode/ @denolfe
|
||||
/.github/ @denolfe
|
||||
/.github/CODEOWNERS @denolfe
|
||||
|
||||
74
.github/workflows/main.yml
vendored
74
.github/workflows/main.yml
vendored
@@ -4,16 +4,12 @@ on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
push:
|
||||
branches: ['main', 'beta']
|
||||
branches: ['main', 'alpha']
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.20.2
|
||||
PNPM_VERSION: 8.15.7
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -23,10 +19,6 @@ jobs:
|
||||
needs_build: ${{ steps.filter.outputs.needs_build }}
|
||||
templates: ${{ steps.filter.outputs.templates }}
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
@@ -57,19 +49,15 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 25
|
||||
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -100,21 +88,18 @@ jobs:
|
||||
tests-unit:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: false # Disable until tests are updated for 3.0
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
@@ -151,19 +136,15 @@ jobs:
|
||||
AWS_REGION: us-east-1
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
@@ -247,7 +228,6 @@ jobs:
|
||||
- fields__collections__Lexical
|
||||
- live-preview
|
||||
- localization
|
||||
- plugin-cloud-storage
|
||||
- plugin-form-builder
|
||||
- plugin-nested-docs
|
||||
- plugin-seo
|
||||
@@ -255,19 +235,15 @@ jobs:
|
||||
- uploads
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
@@ -277,10 +253,6 @@ jobs:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Start LocalStack
|
||||
run: pnpm docker:start
|
||||
if: ${{ matrix.suite == 'plugin-cloud-storage' }}
|
||||
|
||||
- name: Install Playwright
|
||||
run: pnpm exec playwright install --with-deps
|
||||
|
||||
@@ -292,7 +264,6 @@ jobs:
|
||||
with:
|
||||
name: test-results-${{ matrix.suite }}
|
||||
path: test/test-results/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
tests-type-generation:
|
||||
@@ -301,19 +272,15 @@ jobs:
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
@@ -342,14 +309,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
node-version: 18
|
||||
|
||||
- name: Start MongoDB
|
||||
uses: supercharge/mongodb-github-action@1.10.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v18.20.2
|
||||
v18.19.1
|
||||
|
||||
23
.vscode/launch.json
vendored
23
.vscode/launch.json
vendored
@@ -41,13 +41,6 @@
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js auth",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Auth",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev plugin-cloud-storage",
|
||||
"cwd": "${workspaceFolder}",
|
||||
@@ -76,26 +69,36 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js versions",
|
||||
"command": "pnpm run dev versions",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Versions",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js localization",
|
||||
"command": "pnpm run dev localization",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Localization",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js uploads",
|
||||
"command": "pnpm run dev uploads",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Uploads",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "PAYLOAD_BUNDLER=vite pnpm run dev fields",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Fields (Vite)",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"env": {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "pnpm run test:int live-preview",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
|
||||
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = REST_GET(config)
|
||||
export const POST = REST_POST(config)
|
||||
export const DELETE = REST_DELETE(config)
|
||||
export const PATCH = REST_PATCH(config)
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
||||
|
||||
@@ -4,7 +4,7 @@ label: JSON
|
||||
order: 50
|
||||
desc: The JSON field type will store any string in the Database. Learn how to use JSON fields, see examples and options.
|
||||
|
||||
keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
keywords: json, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<Banner>
|
||||
@@ -30,7 +30,6 @@ This field uses the `monaco-react` editor syntax highlighting.
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step)
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
||||
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
||||
| **`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) |
|
||||
@@ -53,7 +52,7 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
|
||||
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
`collections/ExampleCollection.ts
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
@@ -69,67 +68,3 @@ export const ExampleCollection: CollectionConfig = {
|
||||
],
|
||||
}
|
||||
```
|
||||
### JSON Schema Validation
|
||||
|
||||
Payload JSON fields fully support the [JSON schema](https://json-schema.org/) standard. By providing a schema in your field config, the editor will be guided in the admin UI, getting typeahead for properties and their formats automatically. When the document is saved, the default validation will prevent saving any invalid data in the field according to the schema in your config.
|
||||
|
||||
If you only provide a URL to a schema, Payload will fetch the desired schema if it is publicly available. If not, it is recommended to add the schema directly to your config or import it from another file so that it can be implemented consistently in your project.
|
||||
|
||||
|
||||
#### Local JSON Schema
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
fields: [
|
||||
{
|
||||
name: 'customerJSON', // required
|
||||
type: 'json', // required
|
||||
jsonSchema: {
|
||||
uri: 'a://b/foo.json', // required
|
||||
fileMatch: ['a://b/foo.json'], // required
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
enum: ['bar', 'foobar'],
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
],
|
||||
}
|
||||
// {"foo": "bar"} or {"foo": "foobar"} - ok
|
||||
// Attempting to create {"foo": "not-bar"} will throw an error
|
||||
```
|
||||
|
||||
#### Remote JSON Schema
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
fields: [
|
||||
{
|
||||
name: 'customerJSON', // required
|
||||
type: 'json', // required
|
||||
jsonSchema: {
|
||||
uri: 'https://example.com/customer.schema.json', // required
|
||||
fileMatch: ['https://example.com/customer.schema.json'], // required
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
// If 'https://example.com/customer.schema.json' has a JSON schema
|
||||
// {"foo": "bar"} or {"foo": "foobar"} - ok
|
||||
// Attempting to create {"foo": "not-bar"} will throw an error
|
||||
```
|
||||
|
||||
@@ -42,12 +42,11 @@ export const PublicUser: CollectionConfig = {
|
||||
|
||||
**Payload will automatically open up the following queries:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ------------------ | ------------------- |
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`countPublicUsers`** | `count` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
| Query Name | Operation |
|
||||
| ------------------ | ------------------- |
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
|
||||
**And the following mutations:**
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, us
|
||||
|
||||
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
|
||||
|
||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React, Next.js, Vue or Nuxt.js, use the `useLivePreview` hook that Payload provides. In the future, all other major frameworks like Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React or Next.js, use the [`useLivePreview`](#react) React hook that Payload provides. In the future, all other major frameworks like Vue, Svelte, etc will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||
|
||||
By default, all hooks accept the following args:
|
||||
|
||||
@@ -36,10 +36,6 @@ And return the following values:
|
||||
For example, `data?.relatedPosts?.[0]?.title`.
|
||||
</Banner>
|
||||
|
||||
<Banner type="info">
|
||||
It is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
|
||||
</Banner>
|
||||
|
||||
### React
|
||||
|
||||
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
|
||||
@@ -75,40 +71,11 @@ export const PageClient: React.FC<{
|
||||
}
|
||||
```
|
||||
|
||||
### Vue
|
||||
|
||||
If your front-end application is built with Vue 3 or Nuxt 3, you can use the `useLivePreview` composable that Payload provides.
|
||||
|
||||
First, install the `@payloadcms/live-preview-vue` package:
|
||||
|
||||
```bash
|
||||
npm install @payloadcms/live-preview-vue
|
||||
```
|
||||
|
||||
Then, use the `useLivePreview` hook in your Vue component:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { PageData } from '~/types';
|
||||
import { defineProps } from 'vue';
|
||||
import { useLivePreview } from '@payloadcms/live-preview-vue';
|
||||
|
||||
// Fetch the initial data on the parent component or using async state
|
||||
const props = defineProps<{ initialData: PageData }>();
|
||||
|
||||
// The hook will take over from here and keep the preview in sync with the changes you make.
|
||||
// The `data` property will contain the live data of the document only when viewed from the Preview view of the Admin UI.
|
||||
const { data } = useLivePreview<PageData>({
|
||||
initialData: props.initialData,
|
||||
serverURL: "<PAYLOAD_SERVER_URL>",
|
||||
depth: 2,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ data.title }}</h1>
|
||||
</template>
|
||||
```
|
||||
<Banner type="info">
|
||||
If is important that the `depth` argument matches exactly with the depth of your initial page
|
||||
request. The depth property is used to populated relationships and uploads beyond their IDs. See
|
||||
[Depth](../getting-started/concepts#depth) for more information.
|
||||
</Banner>
|
||||
|
||||
## Building your own hook
|
||||
|
||||
|
||||
@@ -164,22 +164,6 @@ const result = await payload.findByID({
|
||||
})
|
||||
```
|
||||
|
||||
#### Count
|
||||
|
||||
```js
|
||||
// Result will be an object with:
|
||||
// {
|
||||
// totalDocs: 10, // count of the documents satisfies query
|
||||
// }
|
||||
const result = await payload.count({
|
||||
collection: 'posts', // required
|
||||
locale: 'en',
|
||||
where: {}, // pass a `where` query here
|
||||
user: dummyUser,
|
||||
overrideAccess: false,
|
||||
})
|
||||
```
|
||||
|
||||
#### Update by ID
|
||||
|
||||
```js
|
||||
|
||||
@@ -90,19 +90,6 @@ Note: Collection slugs must be formatted in kebab-case
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Count",
|
||||
method: "GET",
|
||||
path: "/api/{collection-slug}/count",
|
||||
description: "Count the documents",
|
||||
example: {
|
||||
slug: "count",
|
||||
req: true,
|
||||
res: {
|
||||
totalDocs: 10
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Create",
|
||||
method: "POST",
|
||||
|
||||
@@ -138,7 +138,7 @@ import { CallToAction } from '../blocks/CallToAction'
|
||||
Here's an overview of all the included features:
|
||||
|
||||
| Feature Name | Included by default | Description |
|
||||
|--------------------------------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ------------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`BoldTextFeature`** | Yes | Handles the bold text format |
|
||||
| **`ItalicTextFeature`** | Yes | Handles the italic text format |
|
||||
| **`UnderlineTextFeature`** | Yes | Handles the underline text format |
|
||||
@@ -157,8 +157,7 @@ Here's an overview of all the included features:
|
||||
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
|
||||
| **`BlockQuoteFeature`** | Yes | Allows you to create block-level quotes |
|
||||
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
|
||||
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an <hr> element |
|
||||
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
|
||||
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
|
||||
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
|
||||
|
||||
## Creating your own, custom Feature
|
||||
@@ -235,19 +234,6 @@ This method employs `convertLexicalToHTML` from `@payloadcms/richtext-lexical`,
|
||||
|
||||
Because every `Feature` is able to provide html converters, and because the `htmlFeature` can modify those or provide their own, we need to consolidate them with the default html Converters using the `consolidateHTMLConverters` function.
|
||||
|
||||
#### CSS
|
||||
|
||||
Payload's lexical HTML converter does not generate CSS for you, but it does add classes to the generated HTML. You can use these classes to style the HTML in your frontend.
|
||||
|
||||
Here is some "base" CSS you can use to ensure that nested lists render correctly:
|
||||
|
||||
```css
|
||||
/* Base CSS for Lexical HTML */
|
||||
.nestedListItem, .list-check {
|
||||
list-style-type: none;
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating your own HTML Converter
|
||||
|
||||
HTML Converters are typed as `HTMLConverter`, which contains the node type it should handle, and a function that accepts the serialized node from the lexical editor, and outputs the HTML string. Here's the HTML Converter of the Upload node as an example:
|
||||
|
||||
@@ -40,22 +40,21 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
|
||||
|
||||
### Collection Upload Options
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
|
||||
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
|
||||
| **`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) |
|
||||
| **`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) |
|
||||
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
|
||||
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
| Option | Description |
|
||||
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
|
||||
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
|
||||
| **`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) |
|
||||
| **`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) |
|
||||
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
|
||||
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
|
||||
_An asterisk denotes that a property above is required._
|
||||
|
||||
|
||||
1
emptyModule.js
Normal file
1
emptyModule.js
Normal file
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.12",
|
||||
"version": "3.0.0-alpha.61",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces:": [
|
||||
@@ -18,7 +18,6 @@
|
||||
"build:create-payload-app": "turbo build --filter create-payload-app",
|
||||
"build:db-mongodb": "turbo build --filter db-mongodb",
|
||||
"build:db-postgres": "turbo build --filter db-postgres",
|
||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
||||
"build:eslint-config-payload": "turbo build --filter eslint-config-payload",
|
||||
"build:graphql": "turbo build --filter graphql",
|
||||
"build:live-preview": "turbo build --filter live-preview",
|
||||
@@ -36,7 +35,7 @@
|
||||
"build:plugin-stripe": "turbo build --filter plugin-stripe",
|
||||
"build:richtext-lexical": "turbo build --filter richtext-lexical",
|
||||
"build:richtext-slate": "turbo build --filter richtext-slate",
|
||||
"build:tests": "pnpm --filter payload-test-suite run typecheck",
|
||||
"build:tests": "pnpm --filter test run typecheck",
|
||||
"build:translations": "turbo build --filter translations",
|
||||
"build:ui": "turbo build --filter ui",
|
||||
"clean": "turbo clean",
|
||||
@@ -128,7 +127,7 @@
|
||||
"lint-staged": "^14.0.1",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"next": "^14.3.0-canary.7",
|
||||
"next": "14.2.0-canary.22",
|
||||
"node-mocks-http": "^1.14.1",
|
||||
"nodemon": "3.0.3",
|
||||
"open": "^10.1.0",
|
||||
@@ -164,8 +163,8 @@
|
||||
"react": "18.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.2",
|
||||
"pnpm": ">=8.15.7"
|
||||
"node": ">=18.17.0",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{md,mdx,yml,json}": "prettier --write",
|
||||
@@ -176,7 +175,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/react": "^7.77.0",
|
||||
"ajv": "^8.12.0",
|
||||
"passport-strategy": "1.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.12",
|
||||
"version": "3.0.0-alpha.61",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"homepage": "https://payloadcms.com",
|
||||
@@ -35,7 +35,7 @@
|
||||
"comment-json": "^4.2.3",
|
||||
"degit": "^2.8.4",
|
||||
"detect-package-manager": "^3.0.1",
|
||||
"esprima-next": "^6.0.3",
|
||||
"esprima": "^4.0.1",
|
||||
"execa": "^5.0.0",
|
||||
"figures": "^6.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as p from '@clack/prompts'
|
||||
import { parse, stringify } from 'comment-json'
|
||||
import execa from 'execa'
|
||||
import fs from 'fs'
|
||||
import fse from 'fs-extra'
|
||||
import globby from 'globby'
|
||||
import path from 'path'
|
||||
import { promisify } from 'util'
|
||||
@@ -32,8 +31,6 @@ type InitNextArgs = Pick<CliArgs, '--debug'> & {
|
||||
useDistFiles?: boolean
|
||||
}
|
||||
|
||||
type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
type InitNextResult =
|
||||
| {
|
||||
isSrcDir: boolean
|
||||
@@ -48,22 +45,11 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
|
||||
const nextAppDetails = args.nextAppDetails || (await getNextAppDetails(projectDir))
|
||||
|
||||
if (!nextAppDetails.nextAppDir) {
|
||||
warning(`Could not find app directory in ${projectDir}, creating...`)
|
||||
const createdAppDir = path.resolve(projectDir, nextAppDetails.isSrcDir ? 'src/app' : 'app')
|
||||
fse.mkdirSync(createdAppDir, { recursive: true })
|
||||
nextAppDetails.nextAppDir = createdAppDir
|
||||
}
|
||||
const { hasTopLevelLayout, isSrcDir, nextAppDir } =
|
||||
nextAppDetails || (await getNextAppDetails(projectDir))
|
||||
|
||||
const { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigType } = nextAppDetails
|
||||
|
||||
if (!nextConfigType) {
|
||||
return {
|
||||
isSrcDir,
|
||||
nextAppDir,
|
||||
reason: `Could not determine Next Config type in ${projectDir}. Possibly try renaming next.config.js to next.config.cjs or next.config.mjs.`,
|
||||
success: false,
|
||||
}
|
||||
if (!nextAppDir) {
|
||||
return { isSrcDir, reason: `Could not find app directory in ${projectDir}`, success: false }
|
||||
}
|
||||
|
||||
if (hasTopLevelLayout) {
|
||||
@@ -83,7 +69,6 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const configurationResult = installAndConfigurePayload({
|
||||
...args,
|
||||
nextAppDetails,
|
||||
nextConfigType,
|
||||
useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa
|
||||
})
|
||||
|
||||
@@ -111,23 +96,12 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
|
||||
async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean) {
|
||||
const tsConfigPath = path.resolve(projectDir, 'tsconfig.json')
|
||||
|
||||
// Check if tsconfig.json exists
|
||||
if (!fs.existsSync(tsConfigPath)) {
|
||||
warning(`Could not find tsconfig.json to add @payload-config path.`)
|
||||
return
|
||||
}
|
||||
const userTsConfigContent = await readFile(tsConfigPath, {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
const userTsConfig = parse(userTsConfigContent) as {
|
||||
compilerOptions?: CompilerOptions
|
||||
}
|
||||
|
||||
const hasBaseUrl =
|
||||
userTsConfig?.compilerOptions?.baseUrl && userTsConfig?.compilerOptions?.baseUrl !== '.'
|
||||
const baseUrl = hasBaseUrl ? userTsConfig?.compilerOptions?.baseUrl : './'
|
||||
|
||||
if (!userTsConfig.compilerOptions && !('extends' in userTsConfig)) {
|
||||
userTsConfig.compilerOptions = {}
|
||||
}
|
||||
@@ -138,25 +112,20 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
|
||||
) {
|
||||
userTsConfig.compilerOptions.paths = {
|
||||
...(userTsConfig.compilerOptions.paths || {}),
|
||||
'@payload-config': [`${baseUrl}${isSrcDir ? 'src/' : ''}payload.config.ts`],
|
||||
'@payload-config': [`./${isSrcDir ? 'src/' : ''}payload.config.ts`],
|
||||
}
|
||||
await writeFile(tsConfigPath, stringify(userTsConfig, null, 2), { encoding: 'utf8' })
|
||||
}
|
||||
}
|
||||
|
||||
function installAndConfigurePayload(
|
||||
args: InitNextArgs & {
|
||||
nextAppDetails: NextAppDetails
|
||||
nextConfigType: NextConfigType
|
||||
useDistFiles?: boolean
|
||||
},
|
||||
args: InitNextArgs & { nextAppDetails: NextAppDetails; useDistFiles?: boolean },
|
||||
):
|
||||
| { payloadConfigPath: string; success: true }
|
||||
| { payloadConfigPath?: string; reason: string; success: false } {
|
||||
const {
|
||||
'--debug': debug,
|
||||
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
|
||||
nextConfigType,
|
||||
projectDir,
|
||||
useDistFiles,
|
||||
} = args
|
||||
@@ -203,7 +172,6 @@ function installAndConfigurePayload(
|
||||
logDebug(`nextAppDir: ${nextAppDir}`)
|
||||
logDebug(`projectDir: ${projectDir}`)
|
||||
logDebug(`nextConfigPath: ${nextConfigPath}`)
|
||||
logDebug(`payloadConfigPath: ${path.resolve(projectDir, 'payload.config.ts')}`)
|
||||
|
||||
logDebug(
|
||||
`isSrcDir: ${isSrcDir}. source: ${templateSrcDir}. dest: ${path.dirname(nextConfigPath)}`,
|
||||
@@ -213,7 +181,7 @@ function installAndConfigurePayload(
|
||||
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
|
||||
|
||||
// Wrap next.config.js with withPayload
|
||||
wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
wrapNextConfig({ nextConfigPath })
|
||||
|
||||
return {
|
||||
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
|
||||
@@ -223,10 +191,10 @@ function installAndConfigurePayload(
|
||||
|
||||
async function installDeps(projectDir: string, packageManager: PackageManager, dbType: DbType) {
|
||||
const packagesToInstall = ['payload', '@payloadcms/next', '@payloadcms/richtext-lexical'].map(
|
||||
(pkg) => `${pkg}@beta`,
|
||||
(pkg) => `${pkg}@alpha`,
|
||||
)
|
||||
|
||||
packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)
|
||||
packagesToInstall.push(`@payloadcms/db-${dbType}@alpha`)
|
||||
|
||||
let exitCode = 0
|
||||
switch (packageManager) {
|
||||
@@ -258,7 +226,6 @@ type NextAppDetails = {
|
||||
isSrcDir: boolean
|
||||
nextAppDir?: string
|
||||
nextConfigPath?: string
|
||||
nextConfigType?: NextConfigType
|
||||
}
|
||||
|
||||
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
|
||||
@@ -279,7 +246,6 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
|
||||
await globby(['**/app'], {
|
||||
absolute: true,
|
||||
cwd: projectDir,
|
||||
ignore: ['**/node_modules/**'],
|
||||
onlyDirectories: true,
|
||||
})
|
||||
)?.[0]
|
||||
@@ -288,31 +254,9 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
|
||||
nextAppDir = undefined
|
||||
}
|
||||
|
||||
const configType = await getProjectType(projectDir, nextConfigPath)
|
||||
|
||||
const hasTopLevelLayout = nextAppDir
|
||||
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
|
||||
: false
|
||||
|
||||
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath, nextConfigType: configType }
|
||||
}
|
||||
|
||||
async function getProjectType(projectDir: string, nextConfigPath: string): Promise<'cjs' | 'esm'> {
|
||||
if (nextConfigPath.endsWith('.mjs')) {
|
||||
return 'esm'
|
||||
}
|
||||
if (nextConfigPath.endsWith('.cjs')) {
|
||||
return 'cjs'
|
||||
}
|
||||
|
||||
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
|
||||
const packageJsonType = packageObj.type
|
||||
if (packageJsonType === 'module') {
|
||||
return 'esm'
|
||||
}
|
||||
if (packageJsonType === 'commonjs') {
|
||||
return 'cjs'
|
||||
}
|
||||
|
||||
return 'cjs'
|
||||
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath }
|
||||
}
|
||||
|
||||
@@ -10,18 +10,14 @@ const mongodbReplacement: DbAdapterReplacement = {
|
||||
importReplacement: "import { mongooseAdapter } from '@payloadcms/db-mongodb'",
|
||||
packageName: '@payloadcms/db-mongodb',
|
||||
// Replacement between `// database-adapter-config-start` and `// database-adapter-config-end`
|
||||
configReplacement: [
|
||||
' db: mongooseAdapter({',
|
||||
" url: process.env.DATABASE_URI || '',",
|
||||
' }),',
|
||||
],
|
||||
configReplacement: [' db: mongooseAdapter({', ' url: process.env.DATABASE_URI,', ' }),'],
|
||||
}
|
||||
|
||||
const postgresReplacement: DbAdapterReplacement = {
|
||||
configReplacement: [
|
||||
' db: postgresAdapter({',
|
||||
' pool: {',
|
||||
" connectionString: process.env.DATABASE_URI || '',",
|
||||
' connectionString: process.env.DATABASE_URI,',
|
||||
' },',
|
||||
' }),',
|
||||
],
|
||||
|
||||
@@ -16,7 +16,7 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
|
||||
value: 'mongodb',
|
||||
},
|
||||
postgres: {
|
||||
dbConnectionPrefix: 'postgres://postgres:<password>@127.0.0.1:5432/',
|
||||
dbConnectionPrefix: 'postgres://127.0.0.1:5432/',
|
||||
title: 'PostgreSQL (beta)',
|
||||
value: 'postgres',
|
||||
},
|
||||
|
||||
@@ -18,46 +18,43 @@ export function getValidTemplates(): ProjectTemplate[] {
|
||||
name: 'blank-3.0',
|
||||
type: 'starter',
|
||||
description: 'Blank 3.0 Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank-3.0#beta',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank-3.0',
|
||||
},
|
||||
{
|
||||
name: 'blank',
|
||||
type: 'starter',
|
||||
description: 'Blank Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
type: 'starter',
|
||||
description: 'Website Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/website',
|
||||
},
|
||||
{
|
||||
name: 'ecommerce',
|
||||
type: 'starter',
|
||||
description: 'E-commerce Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/ecommerce',
|
||||
},
|
||||
|
||||
// Remove these until they have been updated for 3.0
|
||||
|
||||
// {
|
||||
// name: 'blank',
|
||||
// type: 'starter',
|
||||
// description: 'Blank Template',
|
||||
// url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
// },
|
||||
// {
|
||||
// name: 'website',
|
||||
// type: 'starter',
|
||||
// description: 'Website Template',
|
||||
// url: 'https://github.com/payloadcms/payload/templates/website',
|
||||
// },
|
||||
// {
|
||||
// name: 'ecommerce',
|
||||
// type: 'starter',
|
||||
// description: 'E-commerce Template',
|
||||
// url: 'https://github.com/payloadcms/payload/templates/ecommerce',
|
||||
// },
|
||||
{
|
||||
name: 'plugin',
|
||||
type: 'plugin',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template#beta',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template',
|
||||
},
|
||||
{
|
||||
name: 'payload-demo',
|
||||
type: 'starter',
|
||||
description: 'Payload demo site at https://demo.payloadcms.com',
|
||||
url: 'https://github.com/payloadcms/public-demo',
|
||||
},
|
||||
{
|
||||
name: 'payload-website',
|
||||
type: 'starter',
|
||||
description: 'Payload website CMS at https://payloadcms.com',
|
||||
url: 'https://github.com/payloadcms/website-cms',
|
||||
},
|
||||
// {
|
||||
// name: 'payload-demo',
|
||||
// type: 'starter',
|
||||
// description: 'Payload demo site at https://demo.payloadcms.com',
|
||||
// url: 'https://github.com/payloadcms/public-demo',
|
||||
// },
|
||||
// {
|
||||
// name: 'payload-website',
|
||||
// type: 'starter',
|
||||
// description: 'Payload website CMS at https://payloadcms.com',
|
||||
// url: 'https://github.com/payloadcms/website-cms',
|
||||
// },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,159 +1,61 @@
|
||||
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'
|
||||
import { parseAndModifyConfigContent, withPayloadImportStatement } from './wrap-next-config.js'
|
||||
import * as p from '@clack/prompts'
|
||||
|
||||
const esmConfigs = {
|
||||
defaultNextConfig: `/** @type {import('next').NextConfig} */
|
||||
const defaultNextConfig = `/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
export default nextConfig;
|
||||
`,
|
||||
nextConfigWithFunc: `const nextConfig = {};
|
||||
export default someFunc(nextConfig);
|
||||
`,
|
||||
nextConfigWithFuncMultiline: `const nextConfig = {};;
|
||||
`
|
||||
|
||||
const nextConfigWithFunc = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default someFunc(nextConfig)
|
||||
`
|
||||
const nextConfigWithFuncMultiline = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default someFunc(
|
||||
nextConfig
|
||||
);
|
||||
`,
|
||||
nextConfigExportNamedDefault: `const nextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
export { wrapped as default };
|
||||
`,
|
||||
nextConfigWithSpread: `const nextConfig = {
|
||||
...someConfig,
|
||||
};
|
||||
export default nextConfig;
|
||||
`,
|
||||
}
|
||||
)
|
||||
`
|
||||
|
||||
const cjsConfigs = {
|
||||
defaultNextConfig: `
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
module.exports = nextConfig;
|
||||
`,
|
||||
anonConfig: `module.exports = {};`,
|
||||
nextConfigWithFunc: `const nextConfig = {};
|
||||
module.exports = someFunc(nextConfig);
|
||||
`,
|
||||
nextConfigWithFuncMultiline: `const nextConfig = {};
|
||||
module.exports = someFunc(
|
||||
nextConfig
|
||||
);
|
||||
`,
|
||||
nextConfigExportNamedDefault: `const nextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
module.exports = wrapped;
|
||||
`,
|
||||
nextConfigWithSpread: `const nextConfig = { ...someConfig };
|
||||
module.exports = nextConfig;
|
||||
`,
|
||||
const nextConfigExportNamedDefault = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
const wrapped = someFunc(asdf)
|
||||
export { wrapped as default }
|
||||
`
|
||||
|
||||
describe('parseAndInsertWithPayload', () => {
|
||||
describe('esm', () => {
|
||||
const configType = 'esm'
|
||||
const importStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
|
||||
// Unsupported: export { wrapped as default }
|
||||
it('should give warning with a named export as default', () => {
|
||||
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
|
||||
|
||||
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(success).toBe(false)
|
||||
|
||||
expect(warnLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Could not automatically wrap'),
|
||||
)
|
||||
})
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(defaultNextConfig)
|
||||
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFunc)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
|
||||
describe('cjs', () => {
|
||||
const configType = 'cjs'
|
||||
const requireStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse anonymous default config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.anonConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload({})')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
|
||||
})
|
||||
it('should parse the config with a named export as default', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
|
||||
})
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFuncMultiline)
|
||||
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
// Unsupported: export { wrapped as default }
|
||||
it('should give warning with a named export as default', () => {
|
||||
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
|
||||
|
||||
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
|
||||
nextConfigExportNamedDefault,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
|
||||
expect(success).toBe(false)
|
||||
|
||||
expect(warnLogSpy).toHaveBeenCalledWith(expect.stringContaining('Could not automatically wrap'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
import type { Program } from 'esprima-next'
|
||||
|
||||
import chalk from 'chalk'
|
||||
import { Syntax, parseModule } from 'esprima-next'
|
||||
import { parseModule } from 'esprima'
|
||||
import fs from 'fs'
|
||||
|
||||
import { warning } from '../utils/log.js'
|
||||
import { log } from '../utils/log.js'
|
||||
|
||||
export const withPayloadStatement = {
|
||||
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
|
||||
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n`,
|
||||
}
|
||||
export const withPayloadImportStatement = `import { withPayload } from '@payloadcms/next'\n`
|
||||
|
||||
type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
export const wrapNextConfig = (args: {
|
||||
nextConfigPath: string
|
||||
nextConfigType: NextConfigType
|
||||
}) => {
|
||||
const { nextConfigPath, nextConfigType: configType } = args
|
||||
export const wrapNextConfig = (args: { nextConfigPath: string }) => {
|
||||
const { nextConfigPath } = args
|
||||
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
|
||||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(
|
||||
configContent,
|
||||
configType,
|
||||
)
|
||||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(configContent)
|
||||
|
||||
if (!success) {
|
||||
return
|
||||
@@ -35,121 +22,72 @@ export const wrapNextConfig = (args: {
|
||||
/**
|
||||
* Parses config content with AST and wraps it with withPayload function
|
||||
*/
|
||||
export function parseAndModifyConfigContent(
|
||||
content: string,
|
||||
configType: NextConfigType,
|
||||
): { modifiedConfigContent: string; success: boolean } {
|
||||
content = withPayloadStatement[configType] + content
|
||||
export function parseAndModifyConfigContent(content: string): {
|
||||
modifiedConfigContent: string
|
||||
success: boolean
|
||||
} {
|
||||
content = withPayloadImportStatement + content
|
||||
const ast = parseModule(content, { loc: true })
|
||||
const exportDefaultDeclaration = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
|
||||
| Directive
|
||||
| undefined
|
||||
|
||||
let ast: Program | undefined
|
||||
try {
|
||||
ast = parseModule(content, { loc: true })
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
warning(`Unable to parse Next config. Error: ${error.message} `)
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
}
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
const exportNamedDeclaration = ast.body.find((p) => p.type === 'ExportNamedDeclaration') as
|
||||
| ExportNamedDeclaration
|
||||
| undefined
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
|
||||
if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
const exportSpecifier = exportNamedDeclaration.specifiers.find(
|
||||
(s) =>
|
||||
s.type === 'ExportSpecifier' &&
|
||||
s.exported?.name === 'default' &&
|
||||
s.local?.type === 'Identifier' &&
|
||||
s.local?.name,
|
||||
)
|
||||
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
if (exportSpecifier) {
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warning('Automatic wrapping of named exports as default not supported yet.')
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
const exportSpecifier = exportNamedDeclaration.specifiers.find(
|
||||
(s) =>
|
||||
s.type === 'ExportSpecifier' &&
|
||||
s.exported?.name === 'default' &&
|
||||
s.local?.type === 'Identifier' &&
|
||||
s.local?.name,
|
||||
)
|
||||
|
||||
if (exportSpecifier) {
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warning('Automatic wrapping of named exports as default not supported yet.')
|
||||
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
warnUserWrapNotSuccessful()
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
} else if (configType === 'cjs') {
|
||||
// Find `module.exports = X`
|
||||
const moduleExports = ast.body.find(
|
||||
(p) =>
|
||||
p.type === Syntax.ExpressionStatement &&
|
||||
p.expression?.type === Syntax.AssignmentExpression &&
|
||||
p.expression.left?.type === Syntax.MemberExpression &&
|
||||
p.expression.left.object?.type === Syntax.Identifier &&
|
||||
p.expression.left.object.name === 'module' &&
|
||||
p.expression.left.property?.type === Syntax.Identifier &&
|
||||
p.expression.left.property.name === 'exports',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
|
||||
if (moduleExports && moduleExports.expression.right?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
moduleExports.expression.right.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
}
|
||||
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warnUserWrapNotSuccessful()
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
function warnUserWrapNotSuccessful() {
|
||||
// Output directions for user to update next.config.js
|
||||
const withPayloadMessage = `
|
||||
|
||||
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)}
|
||||
|
||||
${withPayloadStatement[configType]}
|
||||
import withPayload from '@payloadcms/next/withPayload'
|
||||
|
||||
const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'}
|
||||
export default withPayload(nextConfig)
|
||||
|
||||
`
|
||||
|
||||
|
||||
@@ -20,41 +20,32 @@ export async function writeEnvFile(args: {
|
||||
return
|
||||
}
|
||||
|
||||
const envOutputPath = path.join(projectDir, '.env')
|
||||
|
||||
try {
|
||||
if (fs.existsSync(envOutputPath)) {
|
||||
if (template?.type === 'starter') {
|
||||
// Parse .env file into key/value pairs
|
||||
const envFile = await fs.readFile(path.join(projectDir, '.env.example'), 'utf8')
|
||||
const envWithValues: string[] = envFile
|
||||
.split('\n')
|
||||
.filter((e) => e)
|
||||
.map((line) => {
|
||||
if (line.startsWith('#') || !line.includes('=')) return line
|
||||
if (template?.type === 'starter' && fs.existsSync(path.join(projectDir, '.env.example'))) {
|
||||
// Parse .env file into key/value pairs
|
||||
const envFile = await fs.readFile(path.join(projectDir, '.env.example'), 'utf8')
|
||||
const envWithValues: string[] = envFile
|
||||
.split('\n')
|
||||
.filter((e) => e)
|
||||
.map((line) => {
|
||||
if (line.startsWith('#') || !line.includes('=')) return line
|
||||
|
||||
const split = line.split('=')
|
||||
const key = split[0]
|
||||
let value = split[1]
|
||||
const split = line.split('=')
|
||||
const key = split[0]
|
||||
let value = split[1]
|
||||
|
||||
if (key === 'MONGODB_URI' || key === 'MONGO_URL' || key === 'DATABASE_URI') {
|
||||
value = databaseUri
|
||||
}
|
||||
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
|
||||
value = payloadSecret
|
||||
}
|
||||
if (key === 'MONGODB_URI' || key === 'MONGO_URL' || key === 'DATABASE_URI') {
|
||||
value = databaseUri
|
||||
}
|
||||
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
|
||||
value = payloadSecret
|
||||
}
|
||||
|
||||
return `${key}=${value}`
|
||||
})
|
||||
return `${key}=${value}`
|
||||
})
|
||||
|
||||
// Write new .env file
|
||||
await fs.writeFile(envOutputPath, envWithValues.join('\n'))
|
||||
} else {
|
||||
const existingEnv = await fs.readFile(envOutputPath, 'utf8')
|
||||
const newEnv =
|
||||
existingEnv + `\nDATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}\n`
|
||||
await fs.writeFile(envOutputPath, newEnv)
|
||||
}
|
||||
// Write new .env file
|
||||
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
|
||||
} else {
|
||||
const content = `DATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
|
||||
await fs.outputFile(`${projectDir}/.env`, content)
|
||||
|
||||
@@ -21,12 +21,6 @@ export function helpMessage(): void {
|
||||
console.log(chalk`
|
||||
{bold USAGE}
|
||||
|
||||
{dim Inside of an existing Next.js project}
|
||||
|
||||
{dim $} {bold npx create-payload-app}
|
||||
|
||||
{dim Create a new project from scratch}
|
||||
|
||||
{dim $} {bold npx create-payload-app}
|
||||
{dim $} {bold npx create-payload-app} my-project
|
||||
{dim $} {bold npx create-payload-app} -n my-project -t template-name
|
||||
@@ -86,7 +80,7 @@ export function successfulNextInit(): string {
|
||||
}
|
||||
|
||||
export function moveMessage(args: { nextAppDir: string; projectDir: string }): string {
|
||||
const relativeAppDir = path.relative(process.cwd(), args.nextAppDir)
|
||||
const relativePath = path.relative(process.cwd(), args.nextAppDir)
|
||||
return `
|
||||
${header('Next Steps:')}
|
||||
|
||||
@@ -94,10 +88,7 @@ Payload does not support a top-level layout.tsx file in the app directory.
|
||||
|
||||
${chalk.bold('To continue:')}
|
||||
|
||||
- Create a new directory in ./${relativeAppDir} such as ./${relativeAppDir}/${chalk.bold('(app)')}
|
||||
- Move all files from ./${relativeAppDir} into that directory
|
||||
|
||||
It is recommended to do this from your IDE if your app has existing file references.
|
||||
Move all files from ./${relativePath} to a named directory such as ./${relativePath}/${chalk.bold('(app)')}
|
||||
|
||||
Once moved, rerun the create-payload-app command again.
|
||||
`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.12",
|
||||
"version": "3.0.0-alpha.61",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { Count } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { withSession } from './withSession.js'
|
||||
|
||||
export const count: Count = async function count(
|
||||
this: MongooseAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options: QueryOptions = withSession(this, req.transactionID)
|
||||
|
||||
let hasNearConstraint = false
|
||||
|
||||
if (where) {
|
||||
const constraints = flattenWhereToOperators(where)
|
||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
locale,
|
||||
payload: this.payload,
|
||||
where,
|
||||
})
|
||||
|
||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||
// the correct indexed field
|
||||
options.hint = {
|
||||
_id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
const result = await Model.countDocuments(query, options)
|
||||
|
||||
return {
|
||||
totalDocs: result,
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import { createDatabaseAdapter } from 'payload/database'
|
||||
import type { CollectionModel, GlobalModel } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
import { create } from './create.js'
|
||||
import { createGlobal } from './createGlobal.js'
|
||||
import { createGlobalVersion } from './createGlobalVersion.js'
|
||||
@@ -113,7 +112,6 @@ export function mongooseAdapter({
|
||||
collections: {},
|
||||
connectOptions: connectOptions || {},
|
||||
connection: undefined,
|
||||
count,
|
||||
disableIndexHints,
|
||||
globals: undefined,
|
||||
mongoMemoryServer,
|
||||
@@ -121,6 +119,7 @@ export function mongooseAdapter({
|
||||
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
|
||||
url,
|
||||
versions: {},
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction: transactionOptions ? beginTransaction : undefined,
|
||||
commitTransaction,
|
||||
|
||||
@@ -28,7 +28,6 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
indexSortableFields: this.payload.config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: false,
|
||||
@@ -57,6 +56,12 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
this.autoPluralization === true ? undefined : collection.slug,
|
||||
) as CollectionModel
|
||||
this.collections[collection.slug] = model
|
||||
|
||||
// TS expect error only needed until we launch 2.0.0
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
this.payload.collections[collection.slug] = {
|
||||
config: collection,
|
||||
}
|
||||
})
|
||||
|
||||
const model = buildGlobalModel(this.payload.config)
|
||||
|
||||
@@ -59,12 +59,17 @@ export async function buildSearchParam({
|
||||
let hasCustomID = false
|
||||
|
||||
if (sanitizedPath === '_id') {
|
||||
const customIDFieldType = payload.collections[collectionSlug]?.customIDType
|
||||
const customIDfield = payload.collections[collectionSlug]?.config.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
|
||||
let idFieldType: 'number' | 'text' = 'text'
|
||||
|
||||
if (customIDFieldType) {
|
||||
idFieldType = customIDFieldType
|
||||
if (customIDfield) {
|
||||
if (customIDfield?.type === 'text' || customIDfield?.type === 'number') {
|
||||
idFieldType = customIDfield.type
|
||||
}
|
||||
|
||||
hasCustomID = true
|
||||
}
|
||||
|
||||
@@ -208,11 +213,18 @@ export async function buildSearchParam({
|
||||
} else {
|
||||
;(Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]).forEach(
|
||||
(relationTo) => {
|
||||
const isRelatedToCustomNumberID =
|
||||
payload.collections[relationTo]?.customIDType === 'number'
|
||||
const isRelatedToCustomNumberID = payload.collections[
|
||||
relationTo
|
||||
]?.config?.fields.find((relatedField) => {
|
||||
return (
|
||||
fieldAffectsData(relatedField) &&
|
||||
relatedField.name === 'id' &&
|
||||
relatedField.type === 'number'
|
||||
)
|
||||
})
|
||||
|
||||
if (isRelatedToCustomNumberID) {
|
||||
hasNumberIDRelation = true
|
||||
if (isRelatedToCustomNumberID.type === 'number') hasNumberIDRelation = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { SanitizedConfig, sanitizeConfig } from 'payload/config'
|
||||
import { sanitizeConfig } from 'payload/config'
|
||||
import { Config } from 'payload/config'
|
||||
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
|
||||
|
||||
const config = sanitizeConfig({
|
||||
const config = {
|
||||
localization: {
|
||||
locales: ['en', 'es'],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
} as Config) as SanitizedConfig
|
||||
} as Config
|
||||
|
||||
describe('get localized sort property', () => {
|
||||
it('passes through a non-localized sort property', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config,
|
||||
config: sanitizeConfig(config),
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -30,7 +28,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes an un-localized sort property', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config,
|
||||
config: sanitizeConfig(config),
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -47,7 +45,7 @@ describe('get localized sort property', () => {
|
||||
it('keeps specifically asked-for localized sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title', 'es'],
|
||||
config,
|
||||
config: sanitizeConfig(config),
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -64,7 +62,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes nested sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['group', 'title'],
|
||||
config,
|
||||
config: sanitizeConfig(config),
|
||||
fields: [
|
||||
{
|
||||
name: 'group',
|
||||
@@ -87,7 +85,7 @@ describe('get localized sort property', () => {
|
||||
it('keeps requested locale with nested sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['group', 'title', 'es'],
|
||||
config,
|
||||
config: sanitizeConfig(config),
|
||||
fields: [
|
||||
{
|
||||
name: 'group',
|
||||
@@ -110,7 +108,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within row', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config,
|
||||
config: sanitizeConfig(config),
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
@@ -132,7 +130,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within named tab', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['tab', 'title'],
|
||||
config,
|
||||
config: sanitizeConfig(config),
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
@@ -159,7 +157,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within unnamed tab', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config,
|
||||
config: sanitizeConfig(config),
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
|
||||
@@ -142,10 +142,7 @@ export const sanitizeQueryValue = ({
|
||||
|
||||
if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) {
|
||||
if (operator === 'contains') {
|
||||
formattedValue = {
|
||||
$options: 'i',
|
||||
$regex: formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||
}
|
||||
formattedValue = { $options: 'i', $regex: formattedValue }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,6 @@ export const commitTransaction: CommitTransaction = async function commitTransac
|
||||
}
|
||||
|
||||
await this.sessions[id].commitTransaction()
|
||||
try {
|
||||
await this.sessions[id].endSession()
|
||||
} catch (error) {
|
||||
// ending sessions is only best effort and won't impact anything if it fails since the transaction was committed
|
||||
}
|
||||
await this.sessions[id].endSession()
|
||||
delete this.sessions[id]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.12",
|
||||
"version": "3.0.0-alpha.61",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import type { Count } from 'payload/database'
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { ChainedMethods } from './find/chainMethods.js'
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { chainMethods } from './find/chainMethods.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const count: Count = async function count(
|
||||
this: PostgresAdapter,
|
||||
{ collection, locale, req, where: whereArg },
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
})
|
||||
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const table = this.tables[tableName]
|
||||
|
||||
const { joinAliases, joins, where } = await buildQuery({
|
||||
adapter: this,
|
||||
fields: collectionConfig.fields,
|
||||
locale,
|
||||
tableName,
|
||||
where: whereArg,
|
||||
})
|
||||
|
||||
const selectCountMethods: ChainedMethods = []
|
||||
|
||||
joinAliases.forEach(({ condition, table }) => {
|
||||
selectCountMethods.push({
|
||||
args: [table, condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
})
|
||||
|
||||
Object.entries(joins).forEach(([joinTable, condition]) => {
|
||||
if (joinTable) {
|
||||
selectCountMethods.push({
|
||||
args: [this.tables[joinTable], condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const countResult = await chainMethods({
|
||||
methods: selectCountMethods,
|
||||
query: db
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(DISTINCT ${this.tables[tableName].id})`,
|
||||
})
|
||||
.from(table)
|
||||
.where(where),
|
||||
})
|
||||
|
||||
return { totalDocs: Number(countResult[0].count) }
|
||||
}
|
||||
@@ -120,7 +120,7 @@ export const findMany = async function find({
|
||||
|
||||
const findPromise = db.query[tableName].findMany(findManyArgs)
|
||||
|
||||
if (pagination !== false && (orderedIDs ? orderedIDs?.length <= limit : true)) {
|
||||
if (pagination !== false && (orderedIDs ? orderedIDs?.length >= limit : true)) {
|
||||
const selectCountMethods: ChainedMethods = []
|
||||
|
||||
joinAliases.forEach(({ condition, table }) => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import { createDatabaseAdapter } from 'payload/database'
|
||||
import type { Args, PostgresAdapter } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
import { create } from './create.js'
|
||||
import { createGlobal } from './createGlobal.js'
|
||||
import { createGlobalVersion } from './createGlobalVersion.js'
|
||||
@@ -44,11 +43,9 @@ export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||
export { sql } from 'drizzle-orm'
|
||||
|
||||
export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> {
|
||||
const postgresIDType = args.idType || 'serial'
|
||||
const payloadIDType = postgresIDType ? 'number' : 'text'
|
||||
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = findMigrationDir(args.migrationDir)
|
||||
const idType = args.idType || 'serial'
|
||||
|
||||
return createDatabaseAdapter<PostgresAdapter>({
|
||||
name: 'postgres',
|
||||
@@ -58,7 +55,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
drizzle: undefined,
|
||||
enums: {},
|
||||
fieldConstraints: {},
|
||||
idType: postgresIDType,
|
||||
idType,
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
pgSchema: undefined,
|
||||
@@ -77,13 +74,15 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
beginTransaction,
|
||||
commitTransaction,
|
||||
connect,
|
||||
count,
|
||||
create,
|
||||
createGlobal,
|
||||
createGlobalVersion,
|
||||
createMigration,
|
||||
createVersion,
|
||||
defaultIDType: payloadIDType,
|
||||
/**
|
||||
* This represents how a default ID is treated in Payload as were a field type
|
||||
*/
|
||||
defaultIDType: idType === 'serial' ? 'number' : 'text',
|
||||
deleteMany,
|
||||
deleteOne,
|
||||
deleteVersions,
|
||||
@@ -112,7 +111,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
}
|
||||
|
||||
return {
|
||||
defaultIDType: payloadIDType,
|
||||
defaultIDType: 'number',
|
||||
init: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,85 +225,6 @@ export const getTableColumnFromPath = ({
|
||||
})
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
if (field.hasMany) {
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: `${tableName}_${tableNameSuffix}`,
|
||||
prefix: `${tableName}_${tableNameSuffix}`,
|
||||
})
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
)
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
joins[newTableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
adapter.tables[newTableName].parent,
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
columnName: 'value',
|
||||
constraints,
|
||||
field,
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'text':
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
let tableType = 'texts'
|
||||
let columnName = 'text'
|
||||
if (field.type === 'number') {
|
||||
tableType = 'numbers'
|
||||
columnName = 'number'
|
||||
}
|
||||
newTableName = `${tableName}_${tableType}`
|
||||
const joinConstraints = [
|
||||
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) {
|
||||
joins[newTableName] = and(
|
||||
...joinConstraints,
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
)
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: 'locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
joins[newTableName] = and(...joinConstraints)
|
||||
}
|
||||
|
||||
return {
|
||||
columnName,
|
||||
constraints,
|
||||
field,
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
@@ -564,41 +485,43 @@ export const getTableColumnFromPath = ({
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
// If localized, we go to localized table and set aliasTable to undefined
|
||||
// so it is not picked up below to be used as targetTable
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
default: {
|
||||
if (fieldAffectsData(field)) {
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
// If localized, we go to localized table and set aliasTable to undefined
|
||||
// so it is not picked up below to be used as targetTable
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
|
||||
const parentTable = aliasTable || adapter.tables[tableName]
|
||||
const parentTable = aliasTable || adapter.tables[tableName]
|
||||
|
||||
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
|
||||
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
|
||||
|
||||
aliasTable = undefined
|
||||
aliasTable = undefined
|
||||
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const targetTable = aliasTable || adapter.tables[newTableName]
|
||||
|
||||
selectFields[`${newTableName}.${columnPrefix}${field.name}`] =
|
||||
targetTable[`${columnPrefix}${field.name}`]
|
||||
|
||||
return {
|
||||
columnName: `${columnPrefix}${field.name}`,
|
||||
constraints,
|
||||
field,
|
||||
pathSegments,
|
||||
table: targetTable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const targetTable = aliasTable || adapter.tables[newTableName]
|
||||
|
||||
selectFields[`${newTableName}.${columnPrefix}${field.name}`] =
|
||||
targetTable[`${columnPrefix}${field.name}`]
|
||||
|
||||
return {
|
||||
columnName: `${columnPrefix}${field.name}`,
|
||||
constraints,
|
||||
field,
|
||||
pathSegments,
|
||||
table: targetTable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,13 @@ export const buildTable = ({
|
||||
const columns: Record<string, PgColumnBuilder> = baseColumns
|
||||
const indexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
||||
|
||||
let hasLocalizedField = false
|
||||
let hasLocalizedRelationshipField = false
|
||||
let hasManyTextField: 'index' | boolean = false
|
||||
let hasManyNumberField: 'index' | boolean = false
|
||||
let hasLocalizedManyTextField = false
|
||||
let hasLocalizedManyNumberField = false
|
||||
|
||||
const localesColumns: Record<string, PgColumnBuilder> = {}
|
||||
const localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
||||
let localesTable: GenericTable | PgTableWithColumns<any>
|
||||
@@ -82,7 +89,7 @@ export const buildTable = ({
|
||||
|
||||
const idColType: IDType = setColumnID({ adapter, columns, fields })
|
||||
|
||||
const {
|
||||
;({
|
||||
hasLocalizedField,
|
||||
hasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField,
|
||||
@@ -109,7 +116,7 @@ export const buildTable = ({
|
||||
rootTableIDColType: rootTableIDColType || idColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
})
|
||||
}))
|
||||
|
||||
if (timestamps) {
|
||||
columns.createdAt = timestamp('created_at', {
|
||||
@@ -291,12 +298,11 @@ export const buildTable = ({
|
||||
throwValidationError: true,
|
||||
})
|
||||
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
|
||||
|
||||
const relatedCollectionCustomIDType =
|
||||
adapter.payload.collections[relationshipConfig.slug]?.customIDType
|
||||
|
||||
if (relatedCollectionCustomIDType === 'number') colType = 'numeric'
|
||||
if (relatedCollectionCustomIDType === 'text') colType = 'varchar'
|
||||
const relatedCollectionCustomID = relationshipConfig.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
|
||||
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
|
||||
|
||||
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
|
||||
`${formattedRelationTo}_id`,
|
||||
|
||||
@@ -228,6 +228,7 @@ export const traverseFields = ({
|
||||
prefix: `enum_${newTableName}_`,
|
||||
target: 'enumName',
|
||||
throwValidationError,
|
||||
versions,
|
||||
})
|
||||
|
||||
adapter.enums[enumName] = pgEnum(
|
||||
@@ -248,6 +249,7 @@ export const traverseFields = ({
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
throwValidationError,
|
||||
versions,
|
||||
})
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
@@ -491,7 +493,6 @@ export const traverseFields = ({
|
||||
localized: field.localized,
|
||||
rootTableName,
|
||||
table: adapter.tables[blockTableName],
|
||||
tableLocales: adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
|
||||
})
|
||||
}
|
||||
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName
|
||||
@@ -657,7 +658,7 @@ export const traverseFields = ({
|
||||
indexes,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
newTableName: parentTableName,
|
||||
parentTableName,
|
||||
relationsToBuild,
|
||||
relationships,
|
||||
|
||||
@@ -10,13 +10,9 @@ type Args = {
|
||||
localized: boolean
|
||||
rootTableName: string
|
||||
table: GenericTable
|
||||
tableLocales?: GenericTable
|
||||
}
|
||||
|
||||
const getFlattenedFieldNames = (
|
||||
fields: Field[],
|
||||
prefix: string = '',
|
||||
): { localized?: boolean; name: string }[] => {
|
||||
const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[] => {
|
||||
return fields.reduce((fieldsToUse, field) => {
|
||||
let fieldPrefix = prefix
|
||||
|
||||
@@ -28,7 +24,7 @@ const getFlattenedFieldNames = (
|
||||
}
|
||||
|
||||
if (fieldHasSubFields(field)) {
|
||||
fieldPrefix = 'name' in field ? `${prefix}${field.name}_` : prefix
|
||||
fieldPrefix = 'name' in field ? `${prefix}${field.name}.` : prefix
|
||||
return [...fieldsToUse, ...getFlattenedFieldNames(field.fields, fieldPrefix)]
|
||||
}
|
||||
|
||||
@@ -36,7 +32,7 @@ const getFlattenedFieldNames = (
|
||||
return [
|
||||
...fieldsToUse,
|
||||
...field.tabs.reduce((tabFields, tab) => {
|
||||
fieldPrefix = 'name' in tab ? `${prefix}_${tab.name}` : prefix
|
||||
fieldPrefix = 'name' in tab ? `${prefix}.${tab.name}` : prefix
|
||||
return [
|
||||
...tabFields,
|
||||
...(tabHasName(tab)
|
||||
@@ -48,13 +44,7 @@ const getFlattenedFieldNames = (
|
||||
}
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
return [
|
||||
...fieldsToUse,
|
||||
{
|
||||
name: `${fieldPrefix}${field.name}`,
|
||||
localized: field.localized,
|
||||
},
|
||||
]
|
||||
return [...fieldsToUse, `${fieldPrefix?.replace('.', '_') || ''}${field.name}`]
|
||||
}
|
||||
|
||||
return fieldsToUse
|
||||
@@ -66,30 +56,22 @@ export const validateExistingBlockIsIdentical = ({
|
||||
localized,
|
||||
rootTableName,
|
||||
table,
|
||||
tableLocales,
|
||||
}: Args): void => {
|
||||
const fieldNames = getFlattenedFieldNames(block.fields)
|
||||
|
||||
const missingField =
|
||||
// ensure every field from the config is in the matching table
|
||||
fieldNames.find(({ name, localized }) => {
|
||||
const fieldTable = localized && tableLocales ? tableLocales : table
|
||||
return Object.keys(fieldTable).indexOf(name) === -1
|
||||
}) ||
|
||||
fieldNames.find((name) => Object.keys(table).indexOf(name) === -1) ||
|
||||
// ensure every table column is matched for every field from the config
|
||||
Object.keys(table).find((fieldName) => {
|
||||
if (!['_locale', '_order', '_parentID', '_path', '_uuid'].includes(fieldName)) {
|
||||
return fieldNames.findIndex((field) => field.name) === -1
|
||||
return fieldNames.indexOf(fieldName) === -1
|
||||
}
|
||||
})
|
||||
|
||||
if (missingField) {
|
||||
throw new InvalidConfiguration(
|
||||
`The table ${rootTableName} has multiple blocks with slug ${
|
||||
block.slug
|
||||
}, but the schemas do not match. One block includes the field ${
|
||||
typeof missingField === 'string' ? missingField : missingField.name
|
||||
}, while the other block does not.`,
|
||||
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${missingField}, while the other block does not.`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
@@ -1,7 +0,0 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2022 Payload CMS, LLC <info@payloadcms.com>
|
||||
Portions Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1 +0,0 @@
|
||||
# Nodemailer Email Adapter
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.12",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/email-nodemailer"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"author": "Payload CMS, Inc.",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"nodemailer": "6.9.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"payload": "workspace:*",
|
||||
"@types/nodemailer": "6.4.14"
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import type { Transporter } from 'nodemailer'
|
||||
import type SMTPConnection from 'nodemailer/lib/smtp-connection'
|
||||
import type { EmailAdapter } from 'payload/config'
|
||||
|
||||
import nodemailer from 'nodemailer'
|
||||
import { InvalidConfiguration } from 'payload/errors'
|
||||
|
||||
export type NodemailerAdapterArgs = {
|
||||
defaultFromAddress: string
|
||||
defaultFromName: string
|
||||
skipVerify?: boolean
|
||||
transport?: Transporter
|
||||
transportOptions?: SMTPConnection.Options
|
||||
}
|
||||
|
||||
type NodemailerAdapter = EmailAdapter<unknown>
|
||||
|
||||
/**
|
||||
* Creates an email adapter using nodemailer
|
||||
*
|
||||
* If no email configuration is provided, an ethereal email test account is returned
|
||||
*/
|
||||
export const nodemailerAdapter = async (
|
||||
args?: NodemailerAdapterArgs,
|
||||
): Promise<NodemailerAdapter> => {
|
||||
const { defaultFromAddress, defaultFromName, transport } = await buildEmail(args)
|
||||
|
||||
const adapter: NodemailerAdapter = () => ({
|
||||
defaultFromAddress,
|
||||
defaultFromName,
|
||||
sendEmail: async (message) => {
|
||||
return await transport.sendMail({
|
||||
from: `${defaultFromName} <${defaultFromAddress}>`,
|
||||
...message,
|
||||
})
|
||||
},
|
||||
})
|
||||
return adapter
|
||||
}
|
||||
|
||||
async function buildEmail(emailConfig?: NodemailerAdapterArgs): Promise<{
|
||||
defaultFromAddress: string
|
||||
defaultFromName: string
|
||||
transport: Transporter
|
||||
}> {
|
||||
if (!emailConfig) {
|
||||
const transport = await createMockAccount(emailConfig)
|
||||
if (!transport) throw new InvalidConfiguration('Unable to create Nodemailer test account.')
|
||||
|
||||
return {
|
||||
defaultFromAddress: 'info@payloadcms.com',
|
||||
defaultFromName: 'Payload',
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
||||
// Create or extract transport
|
||||
let transport: Transporter
|
||||
if ('transport' in emailConfig && emailConfig.transport) {
|
||||
;({ transport } = emailConfig)
|
||||
} else if ('transportOptions' in emailConfig && emailConfig.transportOptions) {
|
||||
transport = nodemailer.createTransport(emailConfig.transportOptions)
|
||||
} else {
|
||||
transport = await createMockAccount(emailConfig)
|
||||
}
|
||||
|
||||
if (emailConfig.skipVerify !== false) {
|
||||
await verifyTransport(transport)
|
||||
}
|
||||
|
||||
return {
|
||||
defaultFromAddress: emailConfig.defaultFromAddress,
|
||||
defaultFromName: emailConfig.defaultFromName,
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyTransport(transport: Transporter) {
|
||||
try {
|
||||
await transport.verify()
|
||||
} catch (err: unknown) {
|
||||
console.error({ err, msg: 'Error verifying Nodemailer transport.' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use ethereal.email to create a mock email account
|
||||
*/
|
||||
async function createMockAccount(emailConfig?: NodemailerAdapterArgs) {
|
||||
try {
|
||||
const etherealAccount = await nodemailer.createTestAccount()
|
||||
|
||||
const smtpOptions = {
|
||||
...(emailConfig || {}),
|
||||
auth: {
|
||||
pass: etherealAccount.pass,
|
||||
user: etherealAccount.user,
|
||||
},
|
||||
fromAddress: emailConfig?.defaultFromAddress,
|
||||
fromName: emailConfig?.defaultFromName,
|
||||
host: 'smtp.ethereal.email',
|
||||
port: 587,
|
||||
secure: false,
|
||||
}
|
||||
const transport = nodemailer.createTransport(smtpOptions)
|
||||
const { pass, user, web } = etherealAccount
|
||||
|
||||
console.info('E-mail configured with ethereal.email test account. ')
|
||||
console.info(`Log into mock email provider at ${web}`)
|
||||
console.info(`Mock email account username: ${user}`)
|
||||
console.info(`Mock email account password: ${pass}`)
|
||||
return transport
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
console.error({ err, msg: 'There was a problem setting up the mock email handler' })
|
||||
throw new InvalidConfiguration(
|
||||
`Unable to create Nodemailer test account. Error: ${err.message}`,
|
||||
)
|
||||
}
|
||||
throw new InvalidConfiguration('Unable to create Nodemailer test account.')
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"strict": true,
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [
|
||||
{ "path": "../payload" },
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.12",
|
||||
"version": "3.0.0-alpha.61",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.d.ts",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { PayloadRequest, Where } from 'payload/types'
|
||||
import type { Collection } from 'payload/types'
|
||||
|
||||
import { countOperation } from 'payload/operations'
|
||||
import { isolateObjectProperty } from 'payload/utilities'
|
||||
|
||||
import type { Context } from '../types.js'
|
||||
|
||||
export type Resolver = (
|
||||
_: unknown,
|
||||
args: {
|
||||
data: Record<string, unknown>
|
||||
locale?: string
|
||||
where?: Where
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) => Promise<{ totalDocs: number }>
|
||||
|
||||
export default function countResolver(collection: Collection): Resolver {
|
||||
return async function resolver(_, args, context: Context) {
|
||||
let { req } = context
|
||||
const locale = req.locale
|
||||
const fallbackLocale = req.fallbackLocale
|
||||
req = isolateObjectProperty(req, 'locale')
|
||||
req = isolateObjectProperty(req, 'fallbackLocale')
|
||||
req.locale = args.locale || locale
|
||||
req.fallbackLocale = fallbackLocale
|
||||
|
||||
const options = {
|
||||
collection,
|
||||
req: isolateObjectProperty(req, 'transactionID'),
|
||||
where: args.where,
|
||||
}
|
||||
|
||||
const results = await countOperation(options)
|
||||
return results
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import refresh from '../resolvers/auth/refresh.js'
|
||||
import resetPassword from '../resolvers/auth/resetPassword.js'
|
||||
import unlock from '../resolvers/auth/unlock.js'
|
||||
import verifyEmail from '../resolvers/auth/verifyEmail.js'
|
||||
import countResolver from '../resolvers/collections/count.js'
|
||||
import createResolver from '../resolvers/collections/create.js'
|
||||
import getDeleteResolver from '../resolvers/collections/delete.js'
|
||||
import { docAccessResolver } from '../resolvers/collections/docAccess.js'
|
||||
@@ -184,25 +183,6 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
resolve: findResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`count${pluralName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: `count${pluralName}`,
|
||||
fields: {
|
||||
totalDocs: { type: GraphQLInt },
|
||||
},
|
||||
}),
|
||||
args: {
|
||||
draft: { type: GraphQLBoolean },
|
||||
where: { type: collection.graphQL.whereInputType },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: countResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`docAccess${singularName}`] = {
|
||||
type: buildPolicyType({
|
||||
type: 'collection',
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
|
||||
@@ -39,13 +39,7 @@
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"exports": null,
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
@@ -1,37 +0,0 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = {
|
||||
extends: ['@payloadcms'],
|
||||
overrides: [
|
||||
{
|
||||
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
|
||||
},
|
||||
{
|
||||
files: ['package.json', 'tsconfig.json'],
|
||||
rules: {
|
||||
'perfectionist/sort-array-includes': 'off',
|
||||
'perfectionist/sort-astro-attributes': 'off',
|
||||
'perfectionist/sort-classes': 'off',
|
||||
'perfectionist/sort-enums': 'off',
|
||||
'perfectionist/sort-exports': 'off',
|
||||
'perfectionist/sort-imports': 'off',
|
||||
'perfectionist/sort-interfaces': 'off',
|
||||
'perfectionist/sort-jsx-props': 'off',
|
||||
'perfectionist/sort-keys': 'off',
|
||||
'perfectionist/sort-maps': 'off',
|
||||
'perfectionist/sort-named-exports': 'off',
|
||||
'perfectionist/sort-named-imports': 'off',
|
||||
'perfectionist/sort-object-types': 'off',
|
||||
'perfectionist/sort-objects': 'off',
|
||||
'perfectionist/sort-svelte-attributes': 'off',
|
||||
'perfectionist/sort-union-types': 'off',
|
||||
'perfectionist/sort-vue-attributes': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
root: true,
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": "inline",
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "0.1.0",
|
||||
"description": "The official live preview Vue SDK for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/live-preview-vue"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"author": "Payload CMS, Inc.",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/live-preview": "workspace:^0.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"vue": "^3.0.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
/**
|
||||
* Vue composable to implement Payload CMS Live Preview.
|
||||
*
|
||||
* {@link https://payloadcms.com/docs/live-preview/frontend View the documentation}
|
||||
*/
|
||||
export const useLivePreview = <T>(props: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
initialData: T
|
||||
serverURL: string
|
||||
}): {
|
||||
data: Ref<T>
|
||||
isLoading: Ref<boolean>
|
||||
} => {
|
||||
const { apiRoute, depth, initialData, serverURL } = props
|
||||
const data = ref(initialData) as Ref<T>
|
||||
const isLoading = ref(true)
|
||||
const hasSentReadyMessage = ref(false)
|
||||
|
||||
const onChange = (mergedData: T) => {
|
||||
data.value = mergedData
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
let subscription: (event: MessageEvent) => void
|
||||
|
||||
onMounted(() => {
|
||||
subscription = subscribe({
|
||||
apiRoute,
|
||||
callback: onChange,
|
||||
depth,
|
||||
initialData,
|
||||
serverURL,
|
||||
})
|
||||
|
||||
if (!hasSentReadyMessage.value) {
|
||||
hasSentReadyMessage.value = true
|
||||
|
||||
ready({
|
||||
serverURL,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unsubscribe(subscription)
|
||||
})
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"jsx": "react"
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
"tests",
|
||||
"test",
|
||||
"node_modules",
|
||||
".eslintrc.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.tsx"
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [{ "path": "../payload" }] // db-mongodb depends on payload
|
||||
}
|
||||
@@ -32,13 +32,7 @@
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"exports": null,
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.12",
|
||||
"version": "3.0.0-alpha.61",
|
||||
"main": "./src/index.js",
|
||||
"types": "./src/index.js",
|
||||
"type": "module",
|
||||
@@ -11,8 +11,7 @@
|
||||
"directory": "packages/next"
|
||||
},
|
||||
"scripts": {
|
||||
"build:cjs": "swc ./src/withPayload.js -o ./dist/cjs/withPayload.cjs --config-file .swcrc-cjs",
|
||||
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:cjs && pnpm build:types && pnpm build:webpack && rm dist/prod/index.js",
|
||||
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && pnpm build:webpack && rm dist/prod/index.js",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"build:webpack": "webpack --config webpack.config.js",
|
||||
@@ -28,10 +27,6 @@
|
||||
"require": "./src/index.js",
|
||||
"types": "./src/index.js"
|
||||
},
|
||||
"./withPayload": {
|
||||
"import": "./src/withPayload.js",
|
||||
"require": "./src/withPayload.js"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./src/exports/*.ts",
|
||||
"require": "./src/exports/*.ts",
|
||||
@@ -55,8 +50,7 @@
|
||||
"swc-plugin-transform-remove-imports": "^1.12.1",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"webpack": "^5.78.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"file-type": "16.5.4"
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "6.0.8",
|
||||
@@ -78,9 +72,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"http-status": "1.6.2",
|
||||
"next": "^14.3.0-canary.7",
|
||||
"payload": "workspace:*",
|
||||
"file-type": "16.5.4"
|
||||
"next": "14.2.0-canary.23",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
@@ -91,9 +84,9 @@
|
||||
"require": "./dist/prod/styles.css",
|
||||
"default": "./dist/prod/styles.css"
|
||||
},
|
||||
"./withPayload": {
|
||||
"import": "./dist/withPayload.js",
|
||||
"require": "./dist/cjs/withPayload.cjs"
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./dist/exports/*.js",
|
||||
@@ -104,7 +97,7 @@
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.2"
|
||||
"node": ">=18.17.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -3,7 +3,6 @@ export { GRAPHQL_PLAYGROUND_GET, GRAPHQL_POST } from '../routes/graphql/index.js
|
||||
export {
|
||||
DELETE as REST_DELETE,
|
||||
GET as REST_GET,
|
||||
OPTIONS as REST_OPTIONS,
|
||||
PATCH as REST_PATCH,
|
||||
POST as REST_POST,
|
||||
} from '../routes/rest/index.js'
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
export { traverseFields } from '../utilities/buildFieldSchemaMap/traverseFields.js'
|
||||
export { createPayloadRequest } from '../utilities/createPayloadRequest.js'
|
||||
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
|
||||
export { getNextI18n } from '../utilities/getNextI18n.js'
|
||||
export { getPayloadHMR } from '../utilities/getPayloadHMR.js'
|
||||
export { headersWithCors } from '../utilities/headersWithCors.js'
|
||||
|
||||
@@ -12,7 +12,6 @@ import { createClientConfig } from 'payload/config'
|
||||
import React from 'react'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
|
||||
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
|
||||
import { getRequestLanguage } from '../../utilities/getRequestLanguage.js'
|
||||
import { DefaultEditView } from '../../views/Edit/Default/index.js'
|
||||
import { DefaultListView } from '../../views/List/Default/index.js'
|
||||
@@ -22,16 +21,6 @@ export const metadata = {
|
||||
title: 'Next.js',
|
||||
}
|
||||
|
||||
import { Merriweather } from 'next/font/google'
|
||||
|
||||
const merriweather = Merriweather({
|
||||
display: 'swap',
|
||||
style: ['normal', 'italic'],
|
||||
subsets: ['latin'],
|
||||
variable: '--font-serif',
|
||||
weight: ['400', '900'],
|
||||
})
|
||||
|
||||
export const RootLayout = async ({
|
||||
children,
|
||||
config: configPromise,
|
||||
@@ -50,7 +39,6 @@ export const RootLayout = async ({
|
||||
headers,
|
||||
})
|
||||
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const i18n = await initI18n({ config: config.i18n, context: 'client', language: languageCode })
|
||||
const clientConfig = await createClientConfig({ config, t: i18n.t })
|
||||
|
||||
@@ -88,11 +76,10 @@ export const RootLayout = async ({
|
||||
children,
|
||||
config,
|
||||
i18n,
|
||||
payload,
|
||||
})
|
||||
|
||||
return (
|
||||
<html className={merriweather.variable} dir={dir} lang={languageCode}>
|
||||
<html dir={dir} lang={languageCode}>
|
||||
<body>
|
||||
<RootProvider
|
||||
componentMap={componentMap}
|
||||
|
||||
@@ -77,7 +77,7 @@ export const tempFileHandler: Handler = (options, fieldname, filename) => {
|
||||
}
|
||||
|
||||
export const memHandler: Handler = (options, fieldname, filename) => {
|
||||
const buffers: Buffer[] = []
|
||||
const buffers = []
|
||||
const hash = crypto.createHash('md5')
|
||||
let fileSize = 0
|
||||
let completed = false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const ACCEPTABLE_CONTENT_TYPE = /multipart\/['"()+-_]+(?:; ?['"()+-_]*)+$/i
|
||||
const ACCEPTABLE_CONTENT_TYPE = /^multipart\/['"()+-_]+(?:; ?['"()+-_]*)+$/i
|
||||
const UNACCEPTABLE_METHODS = new Set(['GET', 'HEAD', 'DELETE', 'OPTIONS', 'CONNECT', 'TRACE'])
|
||||
|
||||
const hasBody = (req: Request): boolean => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Busboy from 'busboy'
|
||||
import httpStatus from 'http-status'
|
||||
import { APIError } from 'payload/errors'
|
||||
|
||||
import type { NextFileUploadOptions, NextFileUploadResponse } from './index.js'
|
||||
@@ -18,17 +17,6 @@ type ProcessMultipart = (args: {
|
||||
}) => Promise<NextFileUploadResponse>
|
||||
export const processMultipart: ProcessMultipart = async ({ options, request }) => {
|
||||
let parsingRequest = true
|
||||
|
||||
let fileCount = 0
|
||||
let filesCompleted = 0
|
||||
let allFilesHaveResolved: (value?: unknown) => void
|
||||
let failedResolvingFiles: (err: Error) => void
|
||||
|
||||
const allFilesComplete = new Promise((res, rej) => {
|
||||
allFilesHaveResolved = res
|
||||
failedResolvingFiles = rej
|
||||
})
|
||||
|
||||
const result: NextFileUploadResponse = {
|
||||
fields: undefined,
|
||||
files: undefined,
|
||||
@@ -48,7 +36,6 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
|
||||
|
||||
// Build req.files fields
|
||||
busboy.on('file', (field, file, info) => {
|
||||
fileCount += 1
|
||||
// Parse file name(cutting huge names, decoding, etc..).
|
||||
const { encoding, filename: name, mimeType: mime } = info
|
||||
const filename = parseFileName(options, name)
|
||||
@@ -86,9 +73,7 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
|
||||
debugLog(options, `Aborting upload because of size limit ${field}->${filename}.`)
|
||||
cleanup()
|
||||
parsingRequest = false
|
||||
throw new APIError(options.responseOnLimit, httpStatus.REQUEST_ENTITY_TOO_LARGE, {
|
||||
size: getFileSize(),
|
||||
})
|
||||
throw new APIError(options.responseOnLimit, 413, { size: getFileSize() })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -110,8 +95,6 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
|
||||
return debugLog(options, `Don't add file instance if original name and size are empty`)
|
||||
}
|
||||
|
||||
filesCompleted += 1
|
||||
|
||||
result.files = buildFields(
|
||||
result.files,
|
||||
field,
|
||||
@@ -134,25 +117,19 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
|
||||
request[waitFlushProperty] = []
|
||||
}
|
||||
request[waitFlushProperty].push(writePromise)
|
||||
|
||||
if (filesCompleted === fileCount) {
|
||||
allFilesHaveResolved()
|
||||
}
|
||||
})
|
||||
|
||||
file.on('error', (err) => {
|
||||
uploadTimer.clear()
|
||||
debugLog(options, `File Error: ${err.message}`)
|
||||
cleanup()
|
||||
failedResolvingFiles(err)
|
||||
})
|
||||
|
||||
// Start upload process.
|
||||
debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`)
|
||||
uploadTimer.set()
|
||||
})
|
||||
|
||||
busboy.on('finish', async () => {
|
||||
busboy.on('finish', () => {
|
||||
debugLog(options, `Busboy finished parsing request.`)
|
||||
if (options.parseNested) {
|
||||
result.fields = processNested(result.fields)
|
||||
@@ -160,27 +137,20 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
|
||||
}
|
||||
|
||||
if (request[waitFlushProperty]) {
|
||||
try {
|
||||
await Promise.all(request[waitFlushProperty]).then(() => {
|
||||
delete request[waitFlushProperty]
|
||||
})
|
||||
} catch (err) {
|
||||
debugLog(options, `Error waiting for file write promises: ${err}`)
|
||||
}
|
||||
Promise.all(request[waitFlushProperty]).then(() => {
|
||||
delete request[waitFlushProperty]
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
busboy.on('error', (err) => {
|
||||
debugLog(options, `Busboy error`)
|
||||
parsingRequest = false
|
||||
throw new APIError('Busboy error parsing multipart request', httpStatus.BAD_REQUEST)
|
||||
throw new APIError('Busboy error parsing multipart request', 500)
|
||||
})
|
||||
|
||||
const reader = request.body.getReader()
|
||||
|
||||
// Start parsing request
|
||||
while (parsingRequest) {
|
||||
const { done, value } = await reader.read()
|
||||
|
||||
@@ -193,7 +163,5 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
|
||||
}
|
||||
}
|
||||
|
||||
if (fileCount !== 0) await allFilesComplete
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ let tempCounter = 0
|
||||
export const debugLog = (options: NextFileUploadOptions, msg: string) => {
|
||||
const opts = options || {}
|
||||
if (!opts.debug) return false
|
||||
console.log(`Next-file-upload: ${msg}`) // eslint-disable-line
|
||||
console.log(`Express-file-upload: ${msg}`) // eslint-disable-line
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -287,9 +287,8 @@ export const parseFileName: ParseFileName = (opts, fileName) => {
|
||||
? opts.safeFileNames
|
||||
: SAFE_FILE_NAME_REGEX
|
||||
// Parse file name extension.
|
||||
const parsedFileName = parseFileNameExtension(opts.preserveExtension, parsedName)
|
||||
if (parsedFileName.extension.length)
|
||||
parsedFileName.extension = '.' + parsedFileName.extension.replace(nameRegex, '')
|
||||
let { name, extension } = parseFileNameExtension(opts.preserveExtension, parsedName)
|
||||
if (extension.length) extension = '.' + extension.replace(nameRegex, '')
|
||||
|
||||
return parsedFileName.name.replace(nameRegex, '').concat(parsedFileName.extension)
|
||||
return name.replace(nameRegex, '').concat(extension)
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ import { createHandler } from 'graphql-http/lib/use/fetch'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import { createPayloadRequest } from '../../utilities/createPayloadRequest.js'
|
||||
import { headersWithCors } from '../../utilities/headersWithCors.js'
|
||||
|
||||
const handleError = async (
|
||||
payload: Payload,
|
||||
err: any,
|
||||
debug: boolean,
|
||||
afterErrorHook: CollectionAfterErrorHook,
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
): Promise<GraphQLFormattedError> => {
|
||||
const status = err.originalError.status || httpStatus.INTERNAL_SERVER_ERROR
|
||||
let errorMessage = err.message
|
||||
@@ -39,7 +37,7 @@ const handleError = async (
|
||||
}
|
||||
|
||||
if (afterErrorHook) {
|
||||
;({ response } = afterErrorHook(err, response, null, null) || { response })
|
||||
;({ response } = (await afterErrorHook(err, response, null, null)) || { response })
|
||||
}
|
||||
|
||||
return response
|
||||
@@ -62,7 +60,6 @@ export const getGraphql = async (config: Promise<SanitizedConfig> | SanitizedCon
|
||||
}
|
||||
|
||||
if (!cached.promise) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
cached.promise = new Promise(async (resolve) => {
|
||||
const resolvedConfig = await config
|
||||
const schema = await configToSchema(resolvedConfig)
|
||||
@@ -121,17 +118,13 @@ export const POST =
|
||||
validationRules: (request, args, defaultRules) => defaultRules.concat(validationRules(args)),
|
||||
})(originalRequest)
|
||||
|
||||
const resHeaders = headersWithCors({
|
||||
headers: new Headers(apiResponse.headers),
|
||||
req,
|
||||
})
|
||||
|
||||
const resHeaders = new Headers(apiResponse.headers)
|
||||
for (const key in headers) {
|
||||
resHeaders.append(key, headers[key])
|
||||
}
|
||||
|
||||
return new Response(apiResponse.body, {
|
||||
headers: resHeaders,
|
||||
headers: new Headers(resHeaders),
|
||||
status: apiResponse.status,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,18 +3,12 @@ import { accessOperation } from 'payload/operations'
|
||||
|
||||
import type { BaseRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const access: BaseRouteHandler = async ({ req }) => {
|
||||
const results = await accessOperation({
|
||||
req,
|
||||
})
|
||||
|
||||
return Response.json(results, {
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,10 +3,7 @@ import { forgotPasswordOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const forgotPassword: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { t } = req
|
||||
await forgotPasswordOperation({
|
||||
collection,
|
||||
data: {
|
||||
@@ -19,13 +16,10 @@ export const forgotPassword: CollectionRouteHandler = async ({ collection, req }
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
message: t('general:success'),
|
||||
// TODO(translate)
|
||||
message: 'Success',
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -2,21 +2,11 @@ import { initOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const init: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const initialized = await initOperation({
|
||||
collection: collection.config.slug,
|
||||
req,
|
||||
})
|
||||
|
||||
return Response.json(
|
||||
{ initialized },
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
},
|
||||
)
|
||||
return Response.json({ initialized })
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const login: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { searchParams, t } = req
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
|
||||
const result = await loginOperation({
|
||||
@@ -33,15 +31,13 @@ export const login: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
message: t('authentication:passed'),
|
||||
// TODO(translate)
|
||||
message: 'Auth Passed',
|
||||
...result,
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers({
|
||||
'Set-Cookie': cookie,
|
||||
}),
|
||||
req,
|
||||
headers: new Headers({
|
||||
'Set-Cookie': cookie,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
|
||||
@@ -4,27 +4,18 @@ import { logoutOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const logout: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { t } = req
|
||||
const result = await logoutOperation({
|
||||
const result = logoutOperation({
|
||||
collection,
|
||||
req,
|
||||
})
|
||||
|
||||
const headers = headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return Response.json(
|
||||
{
|
||||
message: t('error:logoutFailed'),
|
||||
message: 'Logout failed.',
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
},
|
||||
)
|
||||
@@ -35,14 +26,15 @@ export const logout: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
payload: req.payload,
|
||||
})
|
||||
|
||||
headers.set('Set-Cookie', expiredCookie)
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
message: t('authentication:logoutSuccessful'),
|
||||
// TODO(translate)
|
||||
message: 'Logout successful.',
|
||||
},
|
||||
{
|
||||
headers,
|
||||
headers: new Headers({
|
||||
'Set-Cookie': expiredCookie,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -4,8 +4,6 @@ import { meOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const me: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const currentToken = extractJWT(req)
|
||||
|
||||
@@ -25,10 +23,6 @@ export const me: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
message: req.t('authentication:account'),
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -5,24 +5,16 @@ import { refreshOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const refresh: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { t } = req
|
||||
const token = typeof req.data?.token === 'string' ? req.data.token : extractJWT(req)
|
||||
|
||||
const headers = headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
})
|
||||
|
||||
if (!token) {
|
||||
return Response.json(
|
||||
{
|
||||
message: t('error:tokenNotProvided'),
|
||||
// TODO(translate)
|
||||
message: 'Token not provided.',
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.UNAUTHORIZED,
|
||||
},
|
||||
)
|
||||
@@ -44,15 +36,16 @@ export const refresh: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
delete result.refreshedToken
|
||||
}
|
||||
|
||||
headers.set('Set-Cookie', cookie)
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
message: t('authentication:tokenRefreshSuccessful'),
|
||||
// TODO(translate)
|
||||
message: 'Token refresh successful',
|
||||
...result,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
headers: new Headers({
|
||||
'Set-Cookie': cookie,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
import httpStatus from 'http-status'
|
||||
import { generatePayloadCookie } from 'payload/auth'
|
||||
import { ValidationError } from 'payload/errors'
|
||||
import { registerFirstUserOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const registerFirstUser: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { data, t } = req
|
||||
|
||||
if (data?.password !== data['confirm-password']) {
|
||||
throw new ValidationError([
|
||||
{
|
||||
field: 'confirm-password',
|
||||
message: req.t('Password and confirm password fields do not match.'),
|
||||
},
|
||||
])
|
||||
}
|
||||
const data = req.data
|
||||
|
||||
const result = await registerFirstUserOperation({
|
||||
collection,
|
||||
@@ -38,16 +26,14 @@ export const registerFirstUser: CollectionRouteHandler = async ({ collection, re
|
||||
return Response.json(
|
||||
{
|
||||
exp: result.exp,
|
||||
message: t('authentication:successfullyRegisteredFirstUser'),
|
||||
// TODO(translate)
|
||||
message: 'Successfully registered first user.',
|
||||
token: result.token,
|
||||
user: result.user,
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers({
|
||||
'Set-Cookie': cookie,
|
||||
}),
|
||||
req,
|
||||
headers: new Headers({
|
||||
'Set-Cookie': cookie,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
|
||||
@@ -4,10 +4,8 @@ import { resetPasswordOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const resetPassword: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { searchParams, t } = req
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
|
||||
const result = await resetPasswordOperation({
|
||||
@@ -32,15 +30,13 @@ export const resetPassword: CollectionRouteHandler = async ({ collection, req })
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
message: t('authentication:passwordResetSuccessfully'),
|
||||
// TODO(translate)
|
||||
message: 'Password reset successfully.',
|
||||
...result,
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers({
|
||||
'Set-Cookie': cookie,
|
||||
}),
|
||||
req,
|
||||
headers: new Headers({
|
||||
'Set-Cookie': cookie,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
|
||||
@@ -3,11 +3,7 @@ import { unlockOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const unlock: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { t } = req
|
||||
|
||||
await unlockOperation({
|
||||
collection,
|
||||
data: { email: req.data.email as string },
|
||||
@@ -16,13 +12,10 @@ export const unlock: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
message: t('general:success'),
|
||||
// TODO(translate)
|
||||
message: 'Success',
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -3,10 +3,7 @@ import { verifyEmailOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const verifyEmail: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
const { t } = req
|
||||
await verifyEmailOperation({
|
||||
collection,
|
||||
req,
|
||||
@@ -15,13 +12,10 @@ export const verifyEmail: CollectionRouteHandlerWithID = async ({ id, collection
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
message: t('authentication:emailVerified'),
|
||||
// TODO(translate)
|
||||
message: 'Email verified successfully.',
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ import httpStatus from 'http-status'
|
||||
import type { FieldSchemaMap } from '../../utilities/buildFieldSchemaMap/types.js'
|
||||
|
||||
import { buildFieldSchemaMap } from '../../utilities/buildFieldSchemaMap/index.js'
|
||||
import { headersWithCors } from '../../utilities/headersWithCors.js'
|
||||
|
||||
let cached = global._payload_fieldSchemaMap
|
||||
|
||||
@@ -28,11 +27,6 @@ export const getFieldSchemaMap = (req: PayloadRequest): FieldSchemaMap => {
|
||||
}
|
||||
|
||||
export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
const headers = headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
})
|
||||
|
||||
try {
|
||||
const reqData: BuildFormStateArgs = req.data as BuildFormStateArgs
|
||||
const { collectionSlug, formState, globalSlug, locale, operation, schemaPath } = reqData
|
||||
@@ -50,20 +44,17 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
|
||||
if (!canAccessAdmin) {
|
||||
return Response.json(null, {
|
||||
headers,
|
||||
status: httpStatus.UNAUTHORIZED,
|
||||
})
|
||||
}
|
||||
// Match the user collection to the global admin config
|
||||
} else if (adminUserSlug !== incomingUserSlug) {
|
||||
return Response.json(null, {
|
||||
headers,
|
||||
status: httpStatus.UNAUTHORIZED,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return Response.json(null, {
|
||||
headers,
|
||||
status: httpStatus.UNAUTHORIZED,
|
||||
})
|
||||
}
|
||||
@@ -93,7 +84,6 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
message: 'Could not find field schema for given path',
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
},
|
||||
)
|
||||
@@ -166,7 +156,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (globalSlug && schemaPath === globalSlug) {
|
||||
if (globalSlug) {
|
||||
resolvedData = await req.payload.findGlobal({
|
||||
slug: globalSlug,
|
||||
depth: 0,
|
||||
@@ -197,18 +187,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
req,
|
||||
})
|
||||
|
||||
// Maintain form state of file
|
||||
if (
|
||||
collectionSlug &&
|
||||
req.payload.collections[collectionSlug]?.config?.upload &&
|
||||
formState &&
|
||||
formState.file
|
||||
) {
|
||||
result.file = formState.file
|
||||
}
|
||||
|
||||
return Response.json(result, {
|
||||
headers,
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
} catch (err) {
|
||||
@@ -217,7 +196,6 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
|
||||
message: 'There was an error building form state',
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { Where } from 'payload/types'
|
||||
|
||||
import httpStatus from 'http-status'
|
||||
import { countOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
export const count: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { where } = req.query as {
|
||||
where?: Where
|
||||
}
|
||||
|
||||
const result = await countOperation({
|
||||
collection,
|
||||
req,
|
||||
where,
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const create: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { searchParams } = req
|
||||
const autosave = searchParams.get('autosave') === 'true'
|
||||
@@ -30,10 +28,6 @@ export const create: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
}),
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.CREATED,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -7,8 +7,6 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const deleteDoc: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { depth, where } = req.query as {
|
||||
depth?: string
|
||||
@@ -22,11 +20,6 @@ export const deleteDoc: CollectionRouteHandler = async ({ collection, req }) =>
|
||||
where,
|
||||
})
|
||||
|
||||
const headers = headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
})
|
||||
|
||||
if (result.errors.length === 0) {
|
||||
const message = req.t('general:deletedCountSuccessfully', {
|
||||
count: result.docs.length,
|
||||
@@ -42,7 +35,6 @@ export const deleteDoc: CollectionRouteHandler = async ({ collection, req }) =>
|
||||
message,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
@@ -62,7 +54,6 @@ export const deleteDoc: CollectionRouteHandler = async ({ collection, req }) =>
|
||||
message,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -4,23 +4,9 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const deleteByID: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
collection,
|
||||
req,
|
||||
}) => {
|
||||
export const deleteByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
|
||||
const id = sanitizeCollectionID({
|
||||
id: incomingID,
|
||||
collectionSlug: collection.config.slug,
|
||||
payload: req.payload,
|
||||
})
|
||||
|
||||
const doc = await deleteByIDOperation({
|
||||
id,
|
||||
collection,
|
||||
@@ -28,18 +14,12 @@ export const deleteByID: CollectionRouteHandlerWithID = async ({
|
||||
req,
|
||||
})
|
||||
|
||||
const headers = headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
})
|
||||
|
||||
if (!doc) {
|
||||
return Response.json(
|
||||
{
|
||||
message: req.t('general:notFound'),
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.NOT_FOUND,
|
||||
},
|
||||
)
|
||||
@@ -51,7 +31,6 @@ export const deleteByID: CollectionRouteHandlerWithID = async ({
|
||||
message: req.t('general:deletedSuccessfully'),
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -3,8 +3,6 @@ import { docAccessOperation } from 'payload/operations'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const docAccess: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
const result = await docAccessOperation({
|
||||
id,
|
||||
@@ -13,10 +11,6 @@ export const docAccess: CollectionRouteHandlerWithID = async ({ id, collection,
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,25 +5,12 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const duplicate: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
collection,
|
||||
req,
|
||||
}) => {
|
||||
export const duplicate: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
// draft defaults to true, unless explicitly set requested as false to prevent the newly duplicated document from being published
|
||||
const draft = searchParams.get('draft') !== 'false'
|
||||
|
||||
const id = sanitizeCollectionID({
|
||||
id: incomingID,
|
||||
collectionSlug: collection.config.slug,
|
||||
payload: req.payload,
|
||||
})
|
||||
|
||||
const doc = await duplicateOperation({
|
||||
id,
|
||||
collection,
|
||||
@@ -42,10 +29,6 @@ export const duplicate: CollectionRouteHandlerWithID = async ({
|
||||
message,
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -6,8 +6,6 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const find: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { depth, draft, limit, page, sort, where } = req.query as {
|
||||
depth?: string
|
||||
@@ -30,10 +28,6 @@ export const find: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,23 +4,10 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const findByID: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
collection,
|
||||
req,
|
||||
}) => {
|
||||
export const findByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
|
||||
const id = sanitizeCollectionID({
|
||||
id: incomingID,
|
||||
collectionSlug: collection.config.slug,
|
||||
payload: req.payload,
|
||||
})
|
||||
|
||||
const result = await findByIDOperation({
|
||||
id,
|
||||
collection,
|
||||
@@ -30,10 +17,6 @@ export const findByID: CollectionRouteHandlerWithID = async ({
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,23 +4,10 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const findVersionByID: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
collection,
|
||||
req,
|
||||
}) => {
|
||||
export const findVersionByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
|
||||
const id = sanitizeCollectionID({
|
||||
id: incomingID,
|
||||
collectionSlug: collection.config.slug,
|
||||
payload: req.payload,
|
||||
})
|
||||
|
||||
const result = await findVersionByIDOperation({
|
||||
id,
|
||||
collection,
|
||||
@@ -29,10 +16,6 @@ export const findVersionByID: CollectionRouteHandlerWithID = async ({
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const findVersions: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { depth, limit, page, sort, where } = req.query as {
|
||||
depth?: string
|
||||
@@ -28,10 +26,6 @@ export const findVersions: CollectionRouteHandler = async ({ collection, req })
|
||||
})
|
||||
|
||||
return Response.json(result, {
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import httpStatus from 'http-status'
|
||||
import { extractJWT } from 'payload/auth'
|
||||
import { findByIDOperation } from 'payload/operations'
|
||||
import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
import { routeError } from '../routeError.js'
|
||||
|
||||
export const preview: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
@@ -26,19 +24,15 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re
|
||||
(config) => config.slug === collection.config.slug,
|
||||
)?.admin?.preview
|
||||
|
||||
const token = extractJWT(req)
|
||||
|
||||
if (typeof generatePreviewURL === 'function') {
|
||||
try {
|
||||
previewURL = await generatePreviewURL(result, {
|
||||
locale: req.locale,
|
||||
req,
|
||||
token,
|
||||
token: req.user?.token,
|
||||
})
|
||||
} catch (err) {
|
||||
return routeError({
|
||||
routeError({
|
||||
collection,
|
||||
config: req.payload.config,
|
||||
err,
|
||||
req,
|
||||
})
|
||||
@@ -46,10 +40,6 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re
|
||||
}
|
||||
|
||||
return Response.json(previewURL, {
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,23 +4,10 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const restoreVersion: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
collection,
|
||||
req,
|
||||
}) => {
|
||||
export const restoreVersion: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
|
||||
const id = sanitizeCollectionID({
|
||||
id: incomingID,
|
||||
collectionSlug: collection.config.slug,
|
||||
payload: req.payload,
|
||||
})
|
||||
|
||||
const result = await restoreVersionOperation({
|
||||
id,
|
||||
collection,
|
||||
@@ -34,10 +21,6 @@ export const restoreVersion: CollectionRouteHandlerWithID = async ({
|
||||
message: req.t('version:restoredSuccessfully'),
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -7,8 +7,6 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandler } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
|
||||
export const update: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const { depth, draft, where } = req.query as {
|
||||
depth?: string
|
||||
@@ -25,11 +23,6 @@ export const update: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
where,
|
||||
})
|
||||
|
||||
const headers = headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
})
|
||||
|
||||
if (result.errors.length === 0) {
|
||||
const message = req.t('general:updatedCountSuccessfully', {
|
||||
count: result.docs.length,
|
||||
@@ -45,7 +38,6 @@ export const update: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
message,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
@@ -64,7 +56,6 @@ export const update: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
message,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -4,25 +4,12 @@ import { isNumber } from 'payload/utilities'
|
||||
|
||||
import type { CollectionRouteHandlerWithID } from '../types.js'
|
||||
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
|
||||
|
||||
export const updateByID: CollectionRouteHandlerWithID = async ({
|
||||
id: incomingID,
|
||||
collection,
|
||||
req,
|
||||
}) => {
|
||||
export const updateByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
|
||||
const { searchParams } = req
|
||||
const depth = searchParams.get('depth')
|
||||
const autosave = searchParams.get('autosave') === 'true'
|
||||
const draft = searchParams.get('draft') === 'true'
|
||||
|
||||
const id = sanitizeCollectionID({
|
||||
id: incomingID,
|
||||
collectionSlug: collection.config.slug,
|
||||
payload: req.payload,
|
||||
})
|
||||
|
||||
const doc = await updateByIDOperation({
|
||||
id,
|
||||
autosave,
|
||||
@@ -44,10 +31,6 @@ export const updateByID: CollectionRouteHandlerWithID = async ({
|
||||
message,
|
||||
},
|
||||
{
|
||||
headers: headersWithCors({
|
||||
headers: new Headers(),
|
||||
req,
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import type { Collection, PayloadRequest } from 'payload/types'
|
||||
|
||||
import getFileType from 'file-type'
|
||||
import fsPromises from 'fs/promises'
|
||||
import httpStatus from 'http-status'
|
||||
import path from 'path'
|
||||
import { APIError } from 'payload/errors'
|
||||
|
||||
import { streamFile } from '../../../next-stream-file/index.js'
|
||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||
import { routeError } from '../routeError.js'
|
||||
import { checkFileAccess } from './checkFileAccess.js'
|
||||
import { getFileTypeFallback } from './getFileTypeFallback.js'
|
||||
|
||||
// /:collectionSlug/file/:filename
|
||||
type Args = {
|
||||
@@ -27,6 +24,13 @@ export const getFile = async ({ collection, filename, req }: Args): Promise<Resp
|
||||
)
|
||||
}
|
||||
|
||||
if (collection.config.upload.disableLocalStorage && !collection.config.upload.handlers) {
|
||||
throw new APIError(
|
||||
`This collection has local storage disabled: ${collection.config.slug}`,
|
||||
httpStatus.BAD_REQUEST,
|
||||
)
|
||||
}
|
||||
|
||||
await checkFileAccess({
|
||||
collection,
|
||||
filename,
|
||||
@@ -44,35 +48,25 @@ export const getFile = async ({ collection, filename, req }: Args): Promise<Resp
|
||||
})
|
||||
}
|
||||
|
||||
if (response instanceof Response) return response
|
||||
return response
|
||||
}
|
||||
|
||||
const fileDir = collection.config.upload?.staticDir || collection.config.slug
|
||||
const filePath = path.resolve(`${fileDir}/${filename}`)
|
||||
|
||||
const stats = await fsPromises.stat(filePath)
|
||||
|
||||
const data = streamFile(filePath)
|
||||
|
||||
const headers = new Headers({
|
||||
'Content-Length': stats.size + '',
|
||||
})
|
||||
|
||||
const fileTypeResult = (await getFileType.fromFile(filePath)) || getFileTypeFallback(filePath)
|
||||
headers.set('Content-Type', fileTypeResult.mime)
|
||||
|
||||
return new Response(data, {
|
||||
headers: headersWithCors({
|
||||
headers,
|
||||
req,
|
||||
headers: new Headers({
|
||||
'content-length': stats.size + '',
|
||||
}),
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
return routeError({
|
||||
collection,
|
||||
config: req.payload.config,
|
||||
err,
|
||||
err: error,
|
||||
req,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
type ReturnType = {
|
||||
ext: string
|
||||
mime: string
|
||||
}
|
||||
|
||||
const extensionMap: {
|
||||
[ext: string]: string
|
||||
} = {
|
||||
css: 'text/css',
|
||||
csv: 'text/csv',
|
||||
htm: 'text/html',
|
||||
html: 'text/html',
|
||||
js: 'application/javascript',
|
||||
json: 'application/json',
|
||||
md: 'text/markdown',
|
||||
svg: 'image/svg+xml',
|
||||
xml: 'application/xml',
|
||||
yml: 'application/x-yaml',
|
||||
}
|
||||
|
||||
export const getFileTypeFallback = (path: string): ReturnType => {
|
||||
const ext = path.split('.').pop() || 'txt'
|
||||
|
||||
return {
|
||||
ext,
|
||||
mime: extensionMap[ext] || 'text/plain',
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user