Compare commits
2 Commits
v3.0.0-bet
...
fix/beta/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa60901385 | ||
|
|
9fc47cea6e |
1
.github/ISSUE_TEMPLATE/1.bug_report_v3.yml
vendored
1
.github/ISSUE_TEMPLATE/1.bug_report_v3.yml
vendored
@@ -39,7 +39,6 @@ body:
|
||||
- 'db-postgres'
|
||||
- 'db-sqlite'
|
||||
- 'db-vercel-postgres'
|
||||
- 'email-nodemailer'
|
||||
- 'plugin: cloud'
|
||||
- 'plugin: cloud-storage'
|
||||
- 'plugin: form-builder'
|
||||
|
||||
@@ -21,11 +21,10 @@ To do so, import the `useField` hook as follows:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import type { TextFieldClientComponent } from 'payload'
|
||||
import { useField } from '@payloadcms/ui'
|
||||
|
||||
export const CustomTextField: TextFieldClientComponent = ({ path }) => {
|
||||
const { value, setValue } = useField({ path }) // highlight-line
|
||||
const CustomTextField: React.FC = () => {
|
||||
const { value, setValue, path } = useField() // highlight-line
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: overview, config, configuration, documentation, Content Management Sys
|
||||
|
||||
Payload is a _config-based_, code-first CMS and application framework. The Payload Config is central to everything that Payload does, allowing for deep configuration of your application through a simple and intuitive API. The Payload Config is a fully-typed JavaScript object that can be infinitely extended upon.
|
||||
|
||||
Everything from your [Database](../database/overview) choice to the appearance of the [Admin Panel](../admin/overview) is fully controlled through the Payload Config. From here you can define [Fields](../fields/overview), add [Localization](./localization), enable [Authentication](../authentication/overview), configure [Access Control](../access-control/overview), and so much more.
|
||||
Everything from your [Database](../database/overview) choice, to the appearance of the [Admin Panel](../admin/overview), is fully controlled through the Payload Config. From here you can define [Fields](../fields/overview), add [Localization](./localization), enable [Authentication](../authentication/overview), configure [Access Control](../access-control/overview), and so much more.
|
||||
|
||||
The Payload Config is a `payload.config.ts` file typically located in the root of your project:
|
||||
|
||||
@@ -29,7 +29,7 @@ The Payload Config is strongly typed and ties directly into Payload's TypeScript
|
||||
|
||||
## Config Options
|
||||
|
||||
To author your Payload Config, first determine which [Database](../database/overview) you'd like to use, then use [Collections](./collections) or [Globals](./globals) to define the schema of your data through [Fields](../fields/overview).
|
||||
To author your Payload Config, first determine which [Database](../database/overview) you'd like to use, then use [Collections](./collections) or [Globals](./globals) to define the schema of your data.
|
||||
|
||||
Here is one of the simplest possible Payload configs:
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ _\* This property is passed directly to [react-datepicker](https://github.com/Ha
|
||||
|
||||
These properties only affect how the date is displayed in the UI. The full date is always stored in the format `YYYY-MM-DDTHH:mm:ss.SSSZ` (e.g. `1999-01-01T8:00:00.000+05:00`).
|
||||
|
||||
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid (unicode date format)[https://date-fns.org/v4.1.0/docs/format].
|
||||
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid (unicode date format)[https://date-fns.org/v2.29.3/docs/format].
|
||||
|
||||
`pickerAppearance` sets the appearance of the **react datepicker**, the options available are `dayAndTime`, `dayOnly`, `timeOnly`, and `monthOnly`. By default, the datepicker will display `dayOnly`.
|
||||
|
||||
|
||||
@@ -467,6 +467,20 @@ Payload is still headless, you will still be able to leverage it completely head
|
||||
}
|
||||
```
|
||||
|
||||
1. `root` endpoints no longer exist on the config. Instead, you must add them to the `/api` folder within your own Payload application.
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
- endpoints: [...]
|
||||
})
|
||||
```
|
||||
|
||||
For more details, see the [Next.js Documentation](https://nextjs.org/docs/app/api-reference/file-conventions/route).
|
||||
|
||||
1. All other endpoint handlers have changed. The args no longer include `res`, and `next`, and the return type now expects a valid HTTP [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) instead of `res.json`, `res.send`, etc.:
|
||||
|
||||
```diff
|
||||
@@ -684,7 +698,7 @@ import { s3Adapter } from '@payloadcms/plugin-cloud-storage/s3'
|
||||
plugins: [
|
||||
cloudStorage({
|
||||
collections: {
|
||||
media: {
|
||||
[mediaSlug]: {
|
||||
adapter: s3Adapter({
|
||||
bucket: process.env.S3_BUCKET,
|
||||
config: {
|
||||
@@ -707,7 +721,7 @@ plugins: [
|
||||
plugins: [
|
||||
s3Storage({
|
||||
collections: {
|
||||
media: true,
|
||||
[mediaSlug]: true,
|
||||
},
|
||||
bucket: process.env.S3_BUCKET,
|
||||
config: {
|
||||
|
||||
@@ -52,8 +52,8 @@ The following operators are available for use in queries:
|
||||
| `all` | The value must contain all values provided in the comma-delimited list. |
|
||||
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
|
||||
| `near` | For distance related to a [Point Field](../fields/point) comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
|
||||
| `within` | For [Point Fields](../fields/point) to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying-within) |
|
||||
| `intersects` | For [Point Fields](../fields/point) to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying-intersects) |
|
||||
| `within` | For [point fields][../fields/point] to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying---within) |
|
||||
| `intersects` | For [point fields][../fields/point] to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying---intersects) |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
|
||||
@@ -14,7 +14,7 @@ Payload offers additional storage adapters to handle file uploads. These adapter
|
||||
| AWS S3 | [`@payloadcms/storage-s3`](https://github.com/payloadcms/payload/tree/beta/packages/storage-s3) |
|
||||
| Azure | [`@payloadcms/storage-azure`](https://github.com/payloadcms/payload/tree/beta/packages/storage-azure) |
|
||||
| Google Cloud Storage | [`@payloadcms/storage-gcs`](https://github.com/payloadcms/payload/tree/beta/packages/storage-gcs) |
|
||||
| Uploadthing | [`@payloadcms/storage-uploadthing`](https://github.com/payloadcms/payload/tree/beta/packages/uploadthing) |
|
||||
|
||||
|
||||
## Vercel Blob Storage
|
||||
[`@payloadcms/storage-vercel-blob`](https://www.npmjs.com/package/@payloadcms/storage-vercel-blob)
|
||||
@@ -43,8 +43,8 @@ export default buildConfig({
|
||||
enabled: true, // Optional, defaults to true
|
||||
// Specify which collections should use Vercel Blob
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': {
|
||||
[Media.slug]: true,
|
||||
[MediaWithPrefix.slug]: {
|
||||
prefix: 'my-prefix',
|
||||
},
|
||||
},
|
||||
@@ -90,8 +90,8 @@ export default buildConfig({
|
||||
plugins: [
|
||||
s3Storage({
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': {
|
||||
[mediaSlug]: true,
|
||||
[mediaWithPrefixSlug]: {
|
||||
prefix,
|
||||
},
|
||||
},
|
||||
@@ -137,8 +137,8 @@ export default buildConfig({
|
||||
plugins: [
|
||||
azureStorage({
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': {
|
||||
[mediaSlug]: true,
|
||||
[mediaWithPrefixSlug]: {
|
||||
prefix,
|
||||
},
|
||||
},
|
||||
@@ -186,8 +186,8 @@ export default buildConfig({
|
||||
plugins: [
|
||||
gcsStorage({
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': {
|
||||
[mediaSlug]: true,
|
||||
[mediaWithPrefixSlug]: {
|
||||
prefix,
|
||||
},
|
||||
},
|
||||
@@ -224,7 +224,7 @@ pnpm add @payloadcms/storage-uploadthing@beta
|
||||
### Usage
|
||||
|
||||
- Configure the `collections` object to specify which collections should use uploadthing. The slug _must_ match one of your existing collection slugs and be an `upload` type.
|
||||
- Get a token from Uploadthing and set it as `token` in the `options` object.
|
||||
- Get an API key from Uploadthing and set it as `apiKey` in the `options` object.
|
||||
- `acl` is optional and defaults to `public-read`.
|
||||
|
||||
```ts
|
||||
@@ -233,10 +233,10 @@ export default buildConfig({
|
||||
plugins: [
|
||||
uploadthingStorage({
|
||||
collections: {
|
||||
media: true,
|
||||
[mediaSlug]: true,
|
||||
},
|
||||
options: {
|
||||
token: process.env.UPLOADTHING_TOKEN,
|
||||
apiKey: process.env.UPLOADTHING_SECRET,
|
||||
acl: 'public-read',
|
||||
},
|
||||
}),
|
||||
@@ -248,7 +248,7 @@ export default buildConfig({
|
||||
|
||||
| Option | Description | Default |
|
||||
| ---------------- | ----------------------------------------------- | ------------- |
|
||||
| `token` | Token from Uploadthing. Required. | |
|
||||
| `apiKey` | API key from Uploadthing. Required. | |
|
||||
| `acl` | Access control list for files that are uploaded | `public-read` |
|
||||
| `logLevel` | Log level for Uploadthing | `info` |
|
||||
| `fetch` | Custom fetch function | `fetch` |
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
DATABASE_URI=mongodb://127.0.0.1/payload-example-email
|
||||
MONGODB_URI=mongodb://127.0.0.1/payload-example-email
|
||||
PAYLOAD_SECRET=
|
||||
NODE_ENV=development
|
||||
PAYLOAD_SECRET=PAYLOAD_EMAIL_EXAMPLE_SECRET_KEY
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:8000
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@payloadcms'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
},
|
||||
"transform": {
|
||||
"react": {
|
||||
"runtime": "automatic",
|
||||
"pragmaFrag": "React.Fragment",
|
||||
"throwIfNamespace": true,
|
||||
"development": false,
|
||||
"useBuiltins": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
@@ -7,31 +7,30 @@ This example demonstrates how to integrate email functionality into Payload.
|
||||
To spin up this example locally, follow these steps:
|
||||
|
||||
1. Clone this repo
|
||||
2. `cp .env.example .env` to copy the example environment variables
|
||||
3. `pnpm install && pnpm dev` to install dependencies and start the dev server
|
||||
2. `cd` into this directory and run `yarn` or `npm install`
|
||||
3. `cp .env.example .env` to copy the example environment variables
|
||||
4. `yarn dev` or `npm run dev` to start the server and seed the database
|
||||
5. open `http://localhost:3000/admin` to access the admin panel
|
||||
5. `open http://localhost:8000/admin` to access the admin panel
|
||||
6. Create your first user
|
||||
|
||||
## How it works
|
||||
|
||||
Email functionality in Payload is configured using adapters. The recommended adapter for most use cases is the [@payloadcms/email-nodemailer](https://www.npmjs.com/package/@payloadcms/email-nodemailer) package.
|
||||
Payload utilizes [NodeMailer](https://nodemailer.com/about/) for email functionality. Once you add your email configuration to `payload.init()`, you send email from anywhere in your application just by calling `payload.sendEmail({})`.
|
||||
|
||||
To enable email, pass your adapter configuration to the `email` property in the Payload Config. This allows Payload to send auth-related emails for password resets, new user verifications, and other email needs.
|
||||
|
||||
1. In the Payload Config file, add your email adapter to the `email` property. For example, the `@payloadcms/email-nodemailer` adapter can be configured for SMTP, SendGrid, or other supported transports. During development, if no configuration is provided, Payload will use a mock service via [ethereal.email](ethereal.email).
|
||||
1. Navigate to `src/server.ts` - this is where your email config gets passed to Payload
|
||||
2. Open `src/email/transport.ts` - here we are defining the email config. You can use an env variable to switch between the mock email transport and live email service.
|
||||
|
||||
Now we can start sending email!
|
||||
|
||||
2. Go to `src/collections/Newsletter.ts` - with an `afterChange` hook, we are sending an email when a new user signs up for the newsletter
|
||||
3. Go to `src/collections/Newsletter.ts` - with an `afterChange` hook, we are sending an email when a new user signs up for the newsletter
|
||||
|
||||
Let's not forget our authentication emails...
|
||||
|
||||
3. Auth-enabled collections have built-in options to verify the user and reset the user password. Open `src/collections/Users.ts` and see how we customize these emails.
|
||||
4. Auth-enabled collections have built-in options to verify the user and reset the user password. Open `src/collections/Users.ts` and see how we customize these emails.
|
||||
|
||||
Speaking of customization...
|
||||
|
||||
4. Take a look at `src/email/generateEmailHTML` and how it compiles a custom template when sending email. You change this to any HTML template of your choosing.
|
||||
5. Take a look at `src/email/generateEmailHTML` and how it compiles a custom template when sending email. You change this to any HTML template of your choosing.
|
||||
|
||||
That's all you need, now you can go ahead and test out this repo by creating a new `user` or `newsletter-signup` and see the email integration in action.
|
||||
|
||||
@@ -41,10 +40,10 @@ To spin up this example locally, follow the [Quick Start](#quick-start).
|
||||
|
||||
## Production
|
||||
|
||||
To run Payload in production, you need to build and start the Admin panel. To do so, follow these steps:
|
||||
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
|
||||
|
||||
1. Invoke the `next build` script by running `pnpm build` or `npm run build` in your project root. This creates a `.next` directory with a production-ready admin bundle.
|
||||
1. Finally run `pnpm start` or `npm run start` to run Node in production and serve Payload from the `.build` directory.
|
||||
1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle.
|
||||
1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory.
|
||||
|
||||
### Deployment
|
||||
|
||||
|
||||
5
examples/email/next-env.d.ts
vendored
5
examples/email/next-env.d.ts
vendored
@@ -1,5 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
@@ -1,8 +0,0 @@
|
||||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default withPayload(nextConfig)
|
||||
5
examples/email/nodemon.json
Normal file
5
examples/email/nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
@@ -1,57 +1,35 @@
|
||||
{
|
||||
"name": "payload-example-email",
|
||||
"version": "1.0.0",
|
||||
"description": "Payload Email integration example.",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/server.js",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"_dev": "cross-env NODE_OPTIONS=--no-deprecation next dev",
|
||||
"build": "cross-env NODE_OPTIONS=--no-deprecation next build",
|
||||
"dev": "cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true NODE_OPTIONS=--no-deprecation next dev",
|
||||
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
|
||||
"generate:schema": "payload-graphql generate:schema",
|
||||
"generate:types": "payload generate:types",
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
|
||||
"dev": "cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
|
||||
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
|
||||
"build:server": "tsc",
|
||||
"build": "yarn copyfiles && yarn build:payload && yarn build:server",
|
||||
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
|
||||
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
|
||||
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/db-mongodb": "beta",
|
||||
"@payloadcms/email-nodemailer": "beta",
|
||||
"@payloadcms/next": "beta",
|
||||
"@payloadcms/richtext-lexical": "beta",
|
||||
"@payloadcms/ui": "beta",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^8.2.0",
|
||||
"ejs": "3.1.10",
|
||||
"graphql": "^16.9.0",
|
||||
"juice": "11.0.0",
|
||||
"next": "15.0.0",
|
||||
"payload": "beta",
|
||||
"react": "19.0.0-rc-65a56d0e-20241020",
|
||||
"react-dom": "19.0.0-rc-65a56d0e-20241020"
|
||||
"express": "^4.17.1",
|
||||
"payload": "latest",
|
||||
"handlebars": "^4.7.7",
|
||||
"inline-css": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/graphql": "beta",
|
||||
"@swc/core": "^1.6.13",
|
||||
"@types/ejs": "^3.1.5",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "15.0.0",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "5.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
|
||||
"@types/express": "^4.17.9",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.19.0",
|
||||
"nodemon": "^2.0.6",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
|
||||
6932
examples/email/pnpm-lock.yaml
generated
6932
examples/email/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
import { generatePageMetadata, NotFoundPage } from '@payloadcms/next/views'
|
||||
|
||||
import { importMap } from '../importMap'
|
||||
|
||||
type Args = {
|
||||
params: Promise<{
|
||||
segments: string[]
|
||||
}>
|
||||
searchParams: Promise<{
|
||||
[key: string]: string | string[]
|
||||
}>
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) =>
|
||||
NotFoundPage({ config, importMap, params, searchParams })
|
||||
|
||||
export default NotFound
|
||||
@@ -1,25 +0,0 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
import { generatePageMetadata, RootPage } from '@payloadcms/next/views'
|
||||
|
||||
import { importMap } from '../importMap'
|
||||
|
||||
type Args = {
|
||||
params: Promise<{
|
||||
segments: string[]
|
||||
}>
|
||||
searchParams: Promise<{
|
||||
[key: string]: string | string[]
|
||||
}>
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const Page = ({ params, searchParams }: Args) =>
|
||||
RootPage({ config, importMap, params, searchParams })
|
||||
|
||||
export default Page
|
||||
@@ -1 +0,0 @@
|
||||
export const importMap = {}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import config from '@payload-config'
|
||||
import { REST_DELETE, REST_GET, REST_OPTIONS, 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)
|
||||
@@ -1,6 +0,0 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = GRAPHQL_PLAYGROUND_GET(config)
|
||||
@@ -1,8 +0,0 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes'
|
||||
|
||||
export const POST = GRAPHQL_POST(config)
|
||||
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { ServerFunctionClient } from 'payload'
|
||||
|
||||
import '@payloadcms/next/css'
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import config from '@payload-config'
|
||||
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
|
||||
import React from 'react'
|
||||
|
||||
import { importMap } from './admin/importMap.js'
|
||||
import './custom.scss'
|
||||
|
||||
type Args = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const serverFunction: ServerFunctionClient = async function (args) {
|
||||
'use server'
|
||||
return handleServerFunctions({
|
||||
...args,
|
||||
config,
|
||||
importMap,
|
||||
})
|
||||
}
|
||||
|
||||
const Layout = ({ children }: Args) => (
|
||||
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
|
||||
{children}
|
||||
</RootLayout>
|
||||
)
|
||||
|
||||
export default Layout
|
||||
@@ -1,12 +1,28 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
import generateEmailHTML from '../email/generateEmailHTML'
|
||||
|
||||
import { generateEmailHTML } from '../email/generateEmailHTML'
|
||||
|
||||
export const Newsletter: CollectionConfig = {
|
||||
const Newsletter: CollectionConfig = {
|
||||
slug: 'newsletter-signups',
|
||||
admin: {
|
||||
defaultColumns: ['name', 'email'],
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [
|
||||
async ({ doc, operation, req }) => {
|
||||
if (operation === 'create') {
|
||||
req.payload.sendEmail({
|
||||
to: doc.email,
|
||||
from: 'sender@example.com',
|
||||
subject: 'Thanks for signing up!',
|
||||
html: await generateEmailHTML({
|
||||
headline: 'Welcome to the newsletter!',
|
||||
content: `<p>${doc.name ? `Hi ${doc.name}!` : 'Hi!'} We'll be in touch soon...</p>`,
|
||||
}),
|
||||
})
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
@@ -18,25 +34,6 @@ export const Newsletter: CollectionConfig = {
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
afterChange: [
|
||||
async ({ doc, operation, req }) => {
|
||||
if (operation === 'create') {
|
||||
req.payload
|
||||
.sendEmail({
|
||||
from: 'sender@example.com',
|
||||
html: await generateEmailHTML({
|
||||
content: `<p>${doc.name ? `Hi ${doc.name}!` : 'Hi!'} We'll be in touch soon...</p>`,
|
||||
headline: 'Welcome to the newsletter!',
|
||||
}),
|
||||
subject: 'Thanks for signing up!',
|
||||
to: doc.email,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error sending email:', error)
|
||||
})
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default Newsletter
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
import { generateForgotPasswordEmail } from '../email/generateForgotPasswordEmail'
|
||||
import { generateVerificationEmail } from '../email/generateVerificationEmail'
|
||||
import generateForgotPasswordEmail from '../email/generateForgotPasswordEmail'
|
||||
import generateVerificationEmail from '../email/generateVerificationEmail'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
auth: {
|
||||
verify: {
|
||||
generateEmailSubject: () => 'Verify your email',
|
||||
generateEmailHTML: generateVerificationEmail,
|
||||
},
|
||||
forgotPassword: {
|
||||
generateEmailSubject: () => 'Reset your password',
|
||||
generateEmailHTML: generateForgotPasswordEmail,
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
auth: {
|
||||
forgotPassword: {
|
||||
generateEmailHTML: generateForgotPasswordEmail,
|
||||
generateEmailSubject: () => 'Reset your password',
|
||||
},
|
||||
verify: {
|
||||
generateEmailHTML: generateVerificationEmail,
|
||||
generateEmailSubject: () => 'Verify your email',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
@@ -25,3 +25,5 @@ export const Users: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default Users
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import ejs from 'ejs'
|
||||
import fs from 'fs'
|
||||
import juice from 'juice'
|
||||
import Handlebars from 'handlebars'
|
||||
import inlineCSS from 'inline-css'
|
||||
import path from 'path'
|
||||
|
||||
export const generateEmailHTML = async (data: any): Promise<string> => {
|
||||
const templatePath = path.join(process.cwd(), 'src/email/template.ejs')
|
||||
const templateContent = fs.readFileSync(templatePath, 'utf8')
|
||||
const template = fs.readFileSync
|
||||
? fs.readFileSync(path.join(__dirname, './template.html'), 'utf8')
|
||||
: ''
|
||||
|
||||
// Compile and render the template with EJS
|
||||
const preInlinedCSS = ejs.render(templateContent, { ...data, cta: data.cta || {} })
|
||||
// Compile the template
|
||||
const getHTML = Handlebars.compile(template)
|
||||
|
||||
// Inline CSS
|
||||
const html = juice(preInlinedCSS)
|
||||
const generateEmailHTML = async (data): Promise<string> => {
|
||||
const preInlinedCSS = getHTML(data)
|
||||
|
||||
return Promise.resolve(html)
|
||||
const html = await inlineCSS(preInlinedCSS, {
|
||||
url: ' ',
|
||||
removeStyleTags: false,
|
||||
})
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
export default generateEmailHTML
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
import type { PayloadRequest } from 'payload'
|
||||
import generateEmailHTML from './generateEmailHTML'
|
||||
|
||||
import { generateEmailHTML } from './generateEmailHTML'
|
||||
|
||||
type ForgotPasswordEmailArgs =
|
||||
| {
|
||||
req?: PayloadRequest
|
||||
token?: string
|
||||
user?: any
|
||||
}
|
||||
| undefined
|
||||
|
||||
export const generateForgotPasswordEmail = async (
|
||||
args: ForgotPasswordEmailArgs,
|
||||
): Promise<string> => {
|
||||
return generateEmailHTML({
|
||||
const generateForgotPasswordEmail = async ({ token }): Promise<string> =>
|
||||
generateEmailHTML({
|
||||
headline: 'Locked out?',
|
||||
content: '<p>Let's get you back in.</p>',
|
||||
cta: {
|
||||
buttonLabel: 'Reset your password',
|
||||
url: `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/reset-password?token=${args?.token}`,
|
||||
url: `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/reset-password?token=${token}`,
|
||||
},
|
||||
headline: 'Locked out?',
|
||||
})
|
||||
}
|
||||
|
||||
export default generateForgotPasswordEmail
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
import { generateEmailHTML } from './generateEmailHTML'
|
||||
import generateEmailHTML from './generateEmailHTML'
|
||||
|
||||
type User = {
|
||||
email: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
type GenerateVerificationEmailArgs = {
|
||||
token: string
|
||||
user: User
|
||||
}
|
||||
|
||||
export const generateVerificationEmail = async (
|
||||
args: GenerateVerificationEmailArgs,
|
||||
): Promise<string> => {
|
||||
const { token, user } = args
|
||||
const generateVerificationEmail = async (args): Promise<string> => {
|
||||
const { user, token } = args
|
||||
|
||||
return generateEmailHTML({
|
||||
headline: 'Verify your account',
|
||||
content: `<p>Hi${user.name ? ' ' + user.name : ''}! Validate your account by clicking the button below.</p>`,
|
||||
cta: {
|
||||
buttonLabel: 'Verify',
|
||||
url: `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/verify?token=${token}&email=${user.email}`,
|
||||
},
|
||||
headline: 'Verify your account',
|
||||
})
|
||||
}
|
||||
|
||||
export default generateVerificationEmail
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style type="text/css">
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body,
|
||||
html,
|
||||
.bg {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
p,
|
||||
em,
|
||||
strong {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 15px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #333333;
|
||||
outline: 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a img {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
font-weight: 900;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 40px;
|
||||
color: #333333;
|
||||
margin: 0 0 25px 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333333;
|
||||
margin: 0 0 25px 0;
|
||||
font-size: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 25px;
|
||||
color: #333333;
|
||||
margin: 0 0 25px 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
color: #333333;
|
||||
margin: 0 0 15px 0;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
color: #333333;
|
||||
font-size: 17px;
|
||||
font-weight: 900;
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
p,
|
||||
td {
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 25px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 15px;
|
||||
margin-left: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
table.hr td {
|
||||
font-size: 0;
|
||||
line-height: 2px;
|
||||
}
|
||||
|
||||
.white {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/********************************
|
||||
MAIN
|
||||
********************************/
|
||||
|
||||
.main {
|
||||
background: white;
|
||||
}
|
||||
|
||||
/********************************
|
||||
MAX WIDTHS
|
||||
********************************/
|
||||
|
||||
.max-width {
|
||||
max-width: 800px;
|
||||
width: 94%;
|
||||
margin: 0 3%;
|
||||
}
|
||||
|
||||
/********************************
|
||||
REUSABLES
|
||||
********************************/
|
||||
|
||||
.padding {
|
||||
padding: 60px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-border {
|
||||
border: 0;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
line-height: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
/********************************
|
||||
PANELS
|
||||
********************************/
|
||||
|
||||
.panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
h1 {
|
||||
font-size: 24px !important;
|
||||
margin: 0 0 20px 0 !important;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px !important;
|
||||
margin: 0 0 20px 0 !important;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px !important;
|
||||
margin: 0 0 20px 0 !important;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 18px !important;
|
||||
margin: 0 0 15px 0 !important;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 15px !important;
|
||||
margin: 0 0 10px !important;
|
||||
}
|
||||
|
||||
.max-width {
|
||||
width: 90% !important;
|
||||
margin: 0 5% !important;
|
||||
}
|
||||
|
||||
td.padding {
|
||||
padding: 30px !important;
|
||||
}
|
||||
|
||||
td.padding-vert {
|
||||
padding-top: 20px !important;
|
||||
padding-bottom: 20px !important;
|
||||
}
|
||||
|
||||
td.padding-horiz {
|
||||
padding-left: 20px !important;
|
||||
padding-right: 20px !important;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
line-height: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="background-color: #f3f3f3; height: 100%">
|
||||
<table height="100%" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#f3f3f3">
|
||||
<tr>
|
||||
<td valign="top" align="left">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table class="max-width" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="spacer"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="padding main">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- LOGO -->
|
||||
<a href="https://payloadcms.com/" target="_blank">
|
||||
<img src="https://payloadcms.com/images/logo-dark.png" width="150"
|
||||
height="auto" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="spacer"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- HEADLINE -->
|
||||
<h1 style="margin: 0 0 30px"><%= headline %></h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- CONTENT -->
|
||||
<%- content %>
|
||||
|
||||
<!-- CTA -->
|
||||
<% if (cta) { %>
|
||||
<div>
|
||||
<a href="<%= cta.url %>" style="
|
||||
background-color: #222222;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
display: inline-block;
|
||||
font-family: sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 200px;
|
||||
-webkit-text-size-adjust: none;
|
||||
">
|
||||
<%= cta.buttonLabel %>
|
||||
</a>
|
||||
</div>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
345
examples/email/src/email/template.html
Normal file
345
examples/email/src/email/template.html
Normal file
@@ -0,0 +1,345 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style type="text/css">
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body,
|
||||
html,
|
||||
.bg {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
p,
|
||||
em,
|
||||
strong {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 15px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #333333;
|
||||
outline: 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a img {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
font-weight: 900;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 40px;
|
||||
color: #333333;
|
||||
margin: 0 0 25px 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333333;
|
||||
margin: 0 0 25px 0;
|
||||
font-size: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 25px;
|
||||
color: #333333;
|
||||
margin: 0 0 25px 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
color: #333333;
|
||||
margin: 0 0 15px 0;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
color: #333333;
|
||||
font-size: 17px;
|
||||
font-weight: 900;
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
p,
|
||||
td {
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 25px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 15px;
|
||||
margin-left: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
table.hr td {
|
||||
font-size: 0;
|
||||
line-height: 2px;
|
||||
}
|
||||
|
||||
.white {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/********************************
|
||||
MAIN
|
||||
********************************/
|
||||
|
||||
.main {
|
||||
background: white;
|
||||
}
|
||||
|
||||
/********************************
|
||||
MAX WIDTHS
|
||||
********************************/
|
||||
|
||||
.max-width {
|
||||
max-width: 800px;
|
||||
width: 94%;
|
||||
margin: 0 3%;
|
||||
}
|
||||
|
||||
/********************************
|
||||
REUSABLES
|
||||
********************************/
|
||||
|
||||
.padding {
|
||||
padding: 60px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-border {
|
||||
border: 0;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
line-height: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
/********************************
|
||||
PANELS
|
||||
********************************/
|
||||
|
||||
.panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
h1 {
|
||||
font-size: 24px !important;
|
||||
margin: 0 0 20px 0 !important;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px !important;
|
||||
margin: 0 0 20px 0 !important;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px !important;
|
||||
margin: 0 0 20px 0 !important;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 18px !important;
|
||||
margin: 0 0 15px 0 !important;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 15px !important;
|
||||
margin: 0 0 10px !important;
|
||||
}
|
||||
|
||||
.max-width {
|
||||
width: 90% !important;
|
||||
margin: 0 5% !important;
|
||||
}
|
||||
|
||||
td.padding {
|
||||
padding: 30px !important;
|
||||
}
|
||||
|
||||
td.padding-vert {
|
||||
padding-top: 20px !important;
|
||||
padding-bottom: 20px !important;
|
||||
}
|
||||
|
||||
td.padding-horiz {
|
||||
padding-left: 20px !important;
|
||||
padding-right: 20px !important;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
line-height: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="background-color: #f3f3f3; height: 100%">
|
||||
<table
|
||||
height="100%"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
border="0"
|
||||
bgcolor="#f3f3f3"
|
||||
style="background-color: #f3f3f3"
|
||||
>
|
||||
<tr>
|
||||
<td valign="top" align="left">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table
|
||||
class="max-width"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
border="0"
|
||||
width="100%"
|
||||
style="width: 100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="spacer"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="padding main">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- LOGO -->
|
||||
<a href="https://payloadcms.com/" target="_blank">
|
||||
<img
|
||||
src="https://payloadcms.com/images/logo-dark.png"
|
||||
width="150"
|
||||
height="auto"
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="spacer"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- HEADLINE -->
|
||||
<h1 style="margin: 0 0 30px">{{headline}}</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- CONTENT -->
|
||||
{{{content}}}
|
||||
|
||||
<!-- CTA -->
|
||||
{{#if cta}}
|
||||
<div>
|
||||
<a
|
||||
href="{{cta.url}}"
|
||||
style="
|
||||
background-color: #222222;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
display: inline-block;
|
||||
font-family: sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 200px;
|
||||
-webkit-text-size-adjust: none;
|
||||
"
|
||||
>
|
||||
{{cta.buttonLabel}}
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
19
examples/email/src/email/transport.ts
Normal file
19
examples/email/src/email/transport.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
let email
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
email = {
|
||||
fromName: 'Payload',
|
||||
fromAddress: 'info@payloadcms.com',
|
||||
transportOptions: {
|
||||
// Configure a custom transport here
|
||||
},
|
||||
}
|
||||
} else {
|
||||
email = {
|
||||
fromName: 'Ethereal Email',
|
||||
fromAddress: 'example@ethereal.com',
|
||||
logMockCredentials: true,
|
||||
}
|
||||
}
|
||||
|
||||
export default email
|
||||
1
examples/email/src/emptyModule.js
Normal file
1
examples/email/src/emptyModule.js
Normal file
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
@@ -1,5 +1,4 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
@@ -7,213 +6,30 @@
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
collections: {
|
||||
'newsletter-signups': NewsletterSignup;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
'newsletter-signups': NewsletterSignupsSelect<false> | NewsletterSignupsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {};
|
||||
globalsSelect: {};
|
||||
locale: null;
|
||||
user: User & {
|
||||
collection: 'users';
|
||||
};
|
||||
jobs?: {
|
||||
tasks: unknown;
|
||||
workflows?: unknown;
|
||||
};
|
||||
}
|
||||
export interface UserAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
login: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "newsletter-signups".
|
||||
*/
|
||||
export interface NewsletterSignup {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
name?: string;
|
||||
email: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
updatedAt: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
_verified?: boolean;
|
||||
_verificationToken?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
resetPasswordExpiration?: string | null;
|
||||
salt?: string | null;
|
||||
hash?: string | null;
|
||||
_verified?: boolean | null;
|
||||
_verificationToken?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'newsletter-signups';
|
||||
value: string | NewsletterSignup;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
password?: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "newsletter-signups_select".
|
||||
*/
|
||||
export interface NewsletterSignupsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
email?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
*/
|
||||
export interface UsersSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
email?: T;
|
||||
resetPasswordToken?: T;
|
||||
resetPasswordExpiration?: T;
|
||||
salt?: T;
|
||||
hash?: T;
|
||||
_verified?: T;
|
||||
_verificationToken?: T;
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
*/
|
||||
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
||||
document?: T;
|
||||
globalSlug?: T;
|
||||
user?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences_select".
|
||||
*/
|
||||
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
||||
user?: T;
|
||||
key?: T;
|
||||
value?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations_select".
|
||||
*/
|
||||
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
batch?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "auth".
|
||||
*/
|
||||
export interface Auth {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
||||
@@ -1,37 +1,42 @@
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
import { buildConfig } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
import { Newsletter } from './collections/Newsletter'
|
||||
import { Users } from './collections/Users'
|
||||
import Users from './collections/Users'
|
||||
import Newsletter from './collections/Newsletter'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
const mockModulePath = path.resolve(__dirname, './emptyModule.js')
|
||||
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
},
|
||||
user: Users.slug,
|
||||
webpack: (config) => ({
|
||||
...config,
|
||||
resolve: {
|
||||
...config?.resolve,
|
||||
alias: [
|
||||
'fs',
|
||||
'handlebars',
|
||||
'inline-css',
|
||||
path.resolve(__dirname, './email/transport'),
|
||||
path.resolve(__dirname, './email/generateEmailHTML'),
|
||||
path.resolve(__dirname, './email/generateForgotPasswordEmail'),
|
||||
path.resolve(__dirname, './email/generateVerificationEmail'),
|
||||
].reduce(
|
||||
(aliases, importPath) => ({
|
||||
...aliases,
|
||||
[importPath]: mockModulePath,
|
||||
}),
|
||||
config.resolve.alias,
|
||||
),
|
||||
},
|
||||
}),
|
||||
},
|
||||
collections: [Newsletter, Users],
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI || '',
|
||||
}),
|
||||
editor: lexicalEditor({}),
|
||||
// For example use case, we are passing nothing to nodemailerAdapter
|
||||
// This will default to using etherial.email
|
||||
email: nodemailerAdapter(),
|
||||
graphQL: {
|
||||
schemaOutputFile: path.resolve(dirname, 'generated-schema.graphql'),
|
||||
},
|
||||
secret: process.env.PAYLOAD_SECRET || '',
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
|
||||
30
examples/email/src/server.ts
Normal file
30
examples/email/src/server.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import express from 'express'
|
||||
import path from 'path'
|
||||
import payload from 'payload'
|
||||
import email from './email/transport'
|
||||
|
||||
require('dotenv').config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
const app = express()
|
||||
|
||||
app.get('/', (_, res) => {
|
||||
res.redirect('/admin')
|
||||
})
|
||||
|
||||
const start = async (): Promise<void> => {
|
||||
await payload.init({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
mongoURL: process.env.MONGODB_URI,
|
||||
express: app,
|
||||
email,
|
||||
onInit: () => {
|
||||
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
|
||||
},
|
||||
})
|
||||
|
||||
app.listen(8000)
|
||||
}
|
||||
|
||||
start()
|
||||
@@ -1,48 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"jsx": "react",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@payload-config": [
|
||||
"src/payload.config.ts"
|
||||
],
|
||||
"@payload-types": [
|
||||
"src/payload-types.ts"
|
||||
]
|
||||
},
|
||||
"target": "ES2017"
|
||||
"payload/generated-types": ["./src/payload-types.ts"],
|
||||
"node_modules/*": ["./node_modules/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"src/mocks/emptyObject.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist", "build"],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
|
||||
7942
examples/email/yarn.lock
Normal file
7942
examples/email/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -71,7 +71,7 @@ const vercelBlobStorageReplacement: StorageAdapterReplacement = {
|
||||
configReplacement: [
|
||||
' vercelBlobStorage({',
|
||||
' collections: {',
|
||||
' media: true,',
|
||||
' [Media.slug]: true,',
|
||||
' },',
|
||||
" token: process.env.BLOB_READ_WRITE_TOKEN || '',",
|
||||
' }),',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -23,11 +23,6 @@
|
||||
"import": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
},
|
||||
"./migration-utils": {
|
||||
"import": "./src/exports/migration-utils.ts",
|
||||
"types": "./src/exports/migration-utils.ts",
|
||||
"default": "./src/exports/migration-utils.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
@@ -46,17 +41,18 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"bson-objectid": "2.0.4",
|
||||
"http-status": "1.6.2",
|
||||
"mongoose": "8.8.1",
|
||||
"mongoose-aggregate-paginate-v2": "1.1.2",
|
||||
"mongoose-paginate-v2": "1.8.5",
|
||||
"mongoose": "6.12.3",
|
||||
"mongoose-aggregate-paginate-v2": "1.0.6",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
"prompts": "2.4.2",
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/mongoose-aggregate-paginate-v2": "1.0.12",
|
||||
"mongodb": "6.10.0",
|
||||
"@types/mongoose-aggregate-paginate-v2": "1.0.6",
|
||||
"mongodb": "4.17.1",
|
||||
"mongodb-memory-server": "^9",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
@@ -69,11 +65,6 @@
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./migration-utils": {
|
||||
"import": "./dist/exports/migration-utils.js",
|
||||
"types": "./dist/exports/migration-utils.d.ts",
|
||||
"default": "./dist/exports/migration-utils.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -60,7 +60,14 @@ export const connect: Connect = async function connect(
|
||||
if (this.ensureIndexes) {
|
||||
await Promise.all(
|
||||
this.payload.config.collections.map(async (coll) => {
|
||||
await this.collections[coll.slug]?.ensureIndexes()
|
||||
await new Promise((resolve, reject) => {
|
||||
this.collections[coll.slug]?.ensureIndexes(function (err) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CountOptions } from 'mongodb'
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { Count, PayloadRequest } from 'payload'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload'
|
||||
@@ -12,7 +12,7 @@ export const count: Count = async function count(
|
||||
{ collection, locale, req = {} as PayloadRequest, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options: CountOptions = await withSession(this, req)
|
||||
const options: QueryOptions = await withSession(this, req)
|
||||
|
||||
let hasNearConstraint = false
|
||||
|
||||
@@ -40,12 +40,7 @@ export const count: Count = async function count(
|
||||
}
|
||||
}
|
||||
|
||||
let result: number
|
||||
if (useEstimatedCount) {
|
||||
result = await Model.estimatedDocumentCount({ session: options.session })
|
||||
} else {
|
||||
result = await Model.countDocuments(query, options)
|
||||
}
|
||||
const result = await Model.countDocuments(query, options)
|
||||
|
||||
return {
|
||||
totalDocs: result,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CountOptions } from 'mongodb'
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { CountGlobalVersions, PayloadRequest } from 'payload'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload'
|
||||
@@ -12,7 +12,7 @@ export const countGlobalVersions: CountGlobalVersions = async function countGlob
|
||||
{ global, locale, req = {} as PayloadRequest, where },
|
||||
) {
|
||||
const Model = this.versions[global]
|
||||
const options: CountOptions = await withSession(this, req)
|
||||
const options: QueryOptions = await withSession(this, req)
|
||||
|
||||
let hasNearConstraint = false
|
||||
|
||||
@@ -40,12 +40,7 @@ export const countGlobalVersions: CountGlobalVersions = async function countGlob
|
||||
}
|
||||
}
|
||||
|
||||
let result: number
|
||||
if (useEstimatedCount) {
|
||||
result = await Model.estimatedDocumentCount({ session: options.session })
|
||||
} else {
|
||||
result = await Model.countDocuments(query, options)
|
||||
}
|
||||
const result = await Model.countDocuments(query, options)
|
||||
|
||||
return {
|
||||
totalDocs: result,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CountOptions } from 'mongodb'
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { CountVersions, PayloadRequest } from 'payload'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload'
|
||||
@@ -12,7 +12,7 @@ export const countVersions: CountVersions = async function countVersions(
|
||||
{ collection, locale, req = {} as PayloadRequest, where },
|
||||
) {
|
||||
const Model = this.versions[collection]
|
||||
const options: CountOptions = await withSession(this, req)
|
||||
const options: QueryOptions = await withSession(this, req)
|
||||
|
||||
let hasNearConstraint = false
|
||||
|
||||
@@ -40,12 +40,7 @@ export const countVersions: CountVersions = async function countVersions(
|
||||
}
|
||||
}
|
||||
|
||||
let result: number
|
||||
if (useEstimatedCount) {
|
||||
result = await Model.estimatedDocumentCount({ session: options.session })
|
||||
} else {
|
||||
result = await Model.countDocuments(query, options)
|
||||
}
|
||||
const result = await Model.countDocuments(query, options)
|
||||
|
||||
return {
|
||||
totalDocs: result,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Types } from 'mongoose'
|
||||
import mongoose from 'mongoose'
|
||||
import {
|
||||
buildVersionCollectionFields,
|
||||
type CreateVersion,
|
||||
@@ -57,7 +57,7 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
},
|
||||
],
|
||||
}
|
||||
if (data.parent instanceof Types.ObjectId) {
|
||||
if (data.parent instanceof mongoose.Types.ObjectId) {
|
||||
parentQuery.$or.push({
|
||||
parent: {
|
||||
$eq: data.parent.toString(),
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export { migrateRelationshipsV2_V3 } from '../predefinedMigrations/migrateRelationshipsV2_V3.js'
|
||||
export { migrateVersionsV1_V2 } from '../predefinedMigrations/migrateVersionsV1_V2.js'
|
||||
@@ -58,6 +58,7 @@ export const find: Find = async function find(
|
||||
// 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
|
||||
const paginationOptions: PaginateOptions = {
|
||||
forceCountFn: hasNearConstraint,
|
||||
lean: true,
|
||||
leanWithId: true,
|
||||
options,
|
||||
|
||||
@@ -64,6 +64,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
// 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
|
||||
const paginationOptions: PaginateOptions = {
|
||||
forceCountFn: hasNearConstraint,
|
||||
lean: true,
|
||||
leanWithId: true,
|
||||
limit,
|
||||
|
||||
@@ -60,6 +60,7 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
// 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
|
||||
const paginationOptions: PaginateOptions = {
|
||||
forceCountFn: hasNearConstraint,
|
||||
lean: true,
|
||||
leanWithId: true,
|
||||
limit,
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
import type { CollationOptions, TransactionOptions } from 'mongodb'
|
||||
import type { MongoMemoryReplSet } from 'mongodb-memory-server'
|
||||
import type { ClientSession, Connection, ConnectOptions, QueryOptions } from 'mongoose'
|
||||
import type {
|
||||
BaseDatabaseAdapter,
|
||||
DatabaseAdapterObj,
|
||||
Payload,
|
||||
TypeWithID,
|
||||
TypeWithVersion,
|
||||
UpdateGlobalArgs,
|
||||
UpdateGlobalVersionArgs,
|
||||
UpdateOneArgs,
|
||||
UpdateVersionArgs,
|
||||
} from 'payload'
|
||||
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload, UpdateOneArgs } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import mongoose from 'mongoose'
|
||||
@@ -145,16 +135,7 @@ declare module 'payload' {
|
||||
}[]
|
||||
sessions: Record<number | string, ClientSession>
|
||||
transactionOptions: TransactionOptions
|
||||
updateGlobal: <T extends Record<string, unknown>>(
|
||||
args: { options?: QueryOptions } & UpdateGlobalArgs<T>,
|
||||
) => Promise<T>
|
||||
updateGlobalVersion: <T extends TypeWithID = TypeWithID>(
|
||||
args: { options?: QueryOptions } & UpdateGlobalVersionArgs<T>,
|
||||
) => Promise<TypeWithVersion<T>>
|
||||
updateOne: (args: { options?: QueryOptions } & UpdateOneArgs) => Promise<Document>
|
||||
updateVersion: <T extends TypeWithID = TypeWithID>(
|
||||
args: { options?: QueryOptions } & UpdateVersionArgs<T>,
|
||||
) => Promise<TypeWithVersion<T>>
|
||||
versions: {
|
||||
[slug: string]: CollectionModel
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ import { getDBName } from './utilities/getDBName.js'
|
||||
|
||||
export const init: Init = function init(this: MongooseAdapter) {
|
||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||
const schema = buildCollectionSchema(collection, this.payload)
|
||||
const schema = buildCollectionSchema(collection, this.payload.config)
|
||||
|
||||
if (collection.versions) {
|
||||
const versionModelName = getDBName({ config: collection, versions: true })
|
||||
|
||||
const versionCollectionFields = buildVersionCollectionFields(this.payload.config, collection)
|
||||
|
||||
const versionSchema = buildSchema(this.payload, versionCollectionFields, {
|
||||
const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
indexSortableFields: this.payload.config.indexSortableFields,
|
||||
@@ -66,7 +66,7 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
) as CollectionModel
|
||||
})
|
||||
|
||||
this.globals = buildGlobalModel(this.payload)
|
||||
this.globals = buildGlobalModel(this.payload.config)
|
||||
|
||||
this.payload.config.globals.forEach((global) => {
|
||||
if (global.versions) {
|
||||
@@ -74,7 +74,7 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
|
||||
const versionGlobalFields = buildVersionGlobalFields(this.payload.config, global)
|
||||
|
||||
const versionSchema = buildSchema(this.payload, versionGlobalFields, {
|
||||
const versionSchema = buildSchema(this.payload.config, versionGlobalFields, {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
indexSortableFields: this.payload.config.indexSortableFields,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PaginateOptions, Schema } from 'mongoose'
|
||||
import type { Payload, SanitizedCollectionConfig } from 'payload'
|
||||
import type { SanitizedCollectionConfig, SanitizedConfig } from 'payload'
|
||||
|
||||
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'
|
||||
import paginate from 'mongoose-paginate-v2'
|
||||
@@ -9,12 +9,12 @@ import { buildSchema } from './buildSchema.js'
|
||||
|
||||
export const buildCollectionSchema = (
|
||||
collection: SanitizedCollectionConfig,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
schemaOptions = {},
|
||||
): Schema => {
|
||||
const schema = buildSchema(payload, collection.fields, {
|
||||
const schema = buildSchema(config, collection.fields, {
|
||||
draftsEnabled: Boolean(typeof collection?.versions === 'object' && collection.versions.drafts),
|
||||
indexSortableFields: payload.config.indexSortableFields,
|
||||
indexSortableFields: config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: collection.timestamps !== false,
|
||||
@@ -34,7 +34,7 @@ export const buildCollectionSchema = (
|
||||
schema.index(indexDefinition, { unique: true })
|
||||
}
|
||||
|
||||
if (payload.config.indexSortableFields && collection.timestamps !== false) {
|
||||
if (config.indexSortableFields && collection.timestamps !== false) {
|
||||
schema.index({ updatedAt: 1 })
|
||||
schema.index({ createdAt: 1 })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Payload } from 'payload'
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
@@ -7,8 +7,8 @@ import type { GlobalModel } from '../types.js'
|
||||
import { getBuildQueryPlugin } from '../queries/buildQuery.js'
|
||||
import { buildSchema } from './buildSchema.js'
|
||||
|
||||
export const buildGlobalModel = (payload: Payload): GlobalModel | null => {
|
||||
if (payload.config.globals && payload.config.globals.length > 0) {
|
||||
export const buildGlobalModel = (config: SanitizedConfig): GlobalModel | null => {
|
||||
if (config.globals && config.globals.length > 0) {
|
||||
const globalsSchema = new mongoose.Schema(
|
||||
{},
|
||||
{ discriminatorKey: 'globalType', minimize: false, timestamps: true },
|
||||
@@ -18,8 +18,8 @@ export const buildGlobalModel = (payload: Payload): GlobalModel | null => {
|
||||
|
||||
const Globals = mongoose.model('globals', globalsSchema, 'globals') as unknown as GlobalModel
|
||||
|
||||
Object.values(payload.config.globals).forEach((globalConfig) => {
|
||||
const globalSchema = buildSchema(payload, globalConfig.fields, {
|
||||
Object.values(config.globals).forEach((globalConfig) => {
|
||||
const globalSchema = buildSchema(config, globalConfig.fields, {
|
||||
options: {
|
||||
minimize: false,
|
||||
},
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import type { IndexOptions, Schema, SchemaOptions, SchemaTypeOptions } from 'mongoose'
|
||||
import type {
|
||||
ArrayField,
|
||||
Block,
|
||||
BlocksField,
|
||||
CheckboxField,
|
||||
CodeField,
|
||||
CollapsibleField,
|
||||
DateField,
|
||||
EmailField,
|
||||
Field,
|
||||
FieldAffectingData,
|
||||
GroupField,
|
||||
JSONField,
|
||||
NonPresentationalField,
|
||||
NumberField,
|
||||
PointField,
|
||||
RadioField,
|
||||
RelationshipField,
|
||||
RichTextField,
|
||||
RowField,
|
||||
SanitizedConfig,
|
||||
SanitizedLocalizationConfig,
|
||||
SelectField,
|
||||
Tab,
|
||||
TabsField,
|
||||
TextareaField,
|
||||
TextField,
|
||||
UploadField,
|
||||
} from 'payload'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
import {
|
||||
type ArrayField,
|
||||
type Block,
|
||||
type BlocksField,
|
||||
type CheckboxField,
|
||||
type CodeField,
|
||||
type CollapsibleField,
|
||||
type DateField,
|
||||
type EmailField,
|
||||
type Field,
|
||||
type FieldAffectingData,
|
||||
type GroupField,
|
||||
type JSONField,
|
||||
type NonPresentationalField,
|
||||
type NumberField,
|
||||
type Payload,
|
||||
type PointField,
|
||||
type RadioField,
|
||||
type RelationshipField,
|
||||
type RichTextField,
|
||||
type RowField,
|
||||
type SanitizedLocalizationConfig,
|
||||
type SelectField,
|
||||
type Tab,
|
||||
type TabsField,
|
||||
type TextareaField,
|
||||
type TextField,
|
||||
type UploadField,
|
||||
} from 'payload'
|
||||
import {
|
||||
fieldAffectsData,
|
||||
fieldIsLocalized,
|
||||
@@ -49,7 +49,7 @@ export type BuildSchemaOptions = {
|
||||
type FieldSchemaGenerator = (
|
||||
field: Field,
|
||||
schema: Schema,
|
||||
config: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
) => void
|
||||
|
||||
@@ -113,7 +113,7 @@ const localizeSchema = (
|
||||
}
|
||||
|
||||
export const buildSchema = (
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
configFields: Field[],
|
||||
buildSchemaOptions: BuildSchemaOptions = {},
|
||||
): Schema => {
|
||||
@@ -145,7 +145,7 @@ export const buildSchema = (
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[field.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(field, schema, payload, buildSchemaOptions)
|
||||
addFieldSchema(field, schema, config, buildSchemaOptions)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -157,13 +157,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
array: (
|
||||
field: ArrayField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
) => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
type: [
|
||||
buildSchema(payload, field.fields, {
|
||||
buildSchema(config, field.fields, {
|
||||
allowIDField: true,
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
@@ -177,13 +177,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
blocks: (
|
||||
field: BlocksField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const fieldSchema = {
|
||||
@@ -191,7 +191,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, fieldSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, fieldSchema, config.localization),
|
||||
})
|
||||
|
||||
field.blocks.forEach((blockItem: Block) => {
|
||||
@@ -200,12 +200,12 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
blockItem.fields.forEach((blockField) => {
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[blockField.type]
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(blockField, blockSchema, payload, buildSchemaOptions)
|
||||
addFieldSchema(blockField, blockSchema, config, buildSchemaOptions)
|
||||
}
|
||||
})
|
||||
|
||||
if (field.localized && payload.config.localization) {
|
||||
payload.config.localization.localeCodes.forEach((localeCode) => {
|
||||
if (field.localized && config.localization) {
|
||||
config.localization.localeCodes.forEach((localeCode) => {
|
||||
// @ts-expect-error Possible incorrect typing in mongoose types, this works
|
||||
schema.path(`${field.name}.${localeCode}`).discriminator(blockItem.slug, blockSchema)
|
||||
})
|
||||
@@ -218,31 +218,31 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
checkbox: (
|
||||
field: CheckboxField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Boolean }
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
code: (
|
||||
field: CodeField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
collapsible: (
|
||||
field: CollapsibleField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
field.fields.forEach((subField: Field) => {
|
||||
@@ -253,38 +253,38 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(subField, schema, payload, buildSchemaOptions)
|
||||
addFieldSchema(subField, schema, config, buildSchemaOptions)
|
||||
}
|
||||
})
|
||||
},
|
||||
date: (
|
||||
field: DateField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Date }
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
email: (
|
||||
field: EmailField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
group: (
|
||||
field: GroupField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const formattedBaseSchema = formatBaseSchema(field, buildSchemaOptions)
|
||||
@@ -297,7 +297,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
|
||||
const baseSchema = {
|
||||
...formattedBaseSchema,
|
||||
type: buildSchema(payload, field.fields, {
|
||||
type: buildSchema(config, field.fields, {
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
indexSortableFields,
|
||||
@@ -310,13 +310,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
json: (
|
||||
field: JSONField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = {
|
||||
@@ -325,13 +325,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
number: (
|
||||
field: NumberField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = {
|
||||
@@ -340,13 +340,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
point: (
|
||||
field: PointField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema: SchemaTypeOptions<unknown> = {
|
||||
@@ -368,7 +368,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
|
||||
if (field.index === true || field.index === undefined) {
|
||||
@@ -377,8 +377,8 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
indexOptions.sparse = true
|
||||
indexOptions.unique = true
|
||||
}
|
||||
if (field.localized && payload.config.localization) {
|
||||
payload.config.localization.locales.forEach((locale) => {
|
||||
if (field.localized && config.localization) {
|
||||
config.localization.locales.forEach((locale) => {
|
||||
schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions)
|
||||
})
|
||||
} else {
|
||||
@@ -389,7 +389,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
radio: (
|
||||
field: RadioField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = {
|
||||
@@ -404,23 +404,21 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
relationship: (
|
||||
field: RelationshipField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
) => {
|
||||
const hasManyRelations = Array.isArray(field.relationTo)
|
||||
let schemaToReturn: { [key: string]: any } = {}
|
||||
|
||||
const valueType = getRelationshipValueType(field, payload)
|
||||
|
||||
if (field.localized && payload.config.localization) {
|
||||
if (field.localized && config.localization) {
|
||||
schemaToReturn = {
|
||||
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
||||
type: config.localization.localeCodes.reduce((locales, locale) => {
|
||||
let localeSchema: { [key: string]: any } = {}
|
||||
|
||||
if (hasManyRelations) {
|
||||
@@ -430,14 +428,14 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
relationTo: { type: String, enum: field.relationTo },
|
||||
value: {
|
||||
type: valueType,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
refPath: `${field.name}.${locale}.relationTo`,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
localeSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
type: valueType,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
ref: field.relationTo,
|
||||
}
|
||||
}
|
||||
@@ -458,7 +456,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
relationTo: { type: String, enum: field.relationTo },
|
||||
value: {
|
||||
type: valueType,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
refPath: `${field.name}.relationTo`,
|
||||
},
|
||||
}
|
||||
@@ -472,7 +470,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
} else {
|
||||
schemaToReturn = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
type: valueType,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
ref: field.relationTo,
|
||||
}
|
||||
|
||||
@@ -491,7 +489,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
richText: (
|
||||
field: RichTextField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = {
|
||||
@@ -500,13 +498,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
row: (
|
||||
field: RowField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
field.fields.forEach((subField: Field) => {
|
||||
@@ -517,14 +515,14 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(subField, schema, payload, buildSchemaOptions)
|
||||
addFieldSchema(subField, schema, config, buildSchemaOptions)
|
||||
}
|
||||
})
|
||||
},
|
||||
select: (
|
||||
field: SelectField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = {
|
||||
@@ -546,14 +544,14 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
field.hasMany ? [baseSchema] : baseSchema,
|
||||
payload.config.localization,
|
||||
config.localization,
|
||||
),
|
||||
})
|
||||
},
|
||||
tabs: (
|
||||
field: TabsField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
field.tabs.forEach((tab) => {
|
||||
@@ -562,7 +560,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
return
|
||||
}
|
||||
const baseSchema = {
|
||||
type: buildSchema(payload, tab.fields, {
|
||||
type: buildSchema(config, tab.fields, {
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
options: {
|
||||
@@ -574,7 +572,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[tab.name]: localizeSchema(tab, baseSchema, payload.config.localization),
|
||||
[tab.name]: localizeSchema(tab, baseSchema, config.localization),
|
||||
})
|
||||
} else {
|
||||
tab.fields.forEach((subField: Field) => {
|
||||
@@ -584,7 +582,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(subField, schema, payload, buildSchemaOptions)
|
||||
addFieldSchema(subField, schema, config, buildSchemaOptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -593,7 +591,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
text: (
|
||||
field: TextField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = {
|
||||
@@ -602,35 +600,33 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
textarea: (
|
||||
field: TextareaField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(field, baseSchema, config.localization),
|
||||
})
|
||||
},
|
||||
upload: (
|
||||
field: UploadField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
config: SanitizedConfig,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const hasManyRelations = Array.isArray(field.relationTo)
|
||||
let schemaToReturn: { [key: string]: any } = {}
|
||||
|
||||
const valueType = getRelationshipValueType(field, payload)
|
||||
|
||||
if (field.localized && payload.config.localization) {
|
||||
if (field.localized && config.localization) {
|
||||
schemaToReturn = {
|
||||
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
||||
type: config.localization.localeCodes.reduce((locales, locale) => {
|
||||
let localeSchema: { [key: string]: any } = {}
|
||||
|
||||
if (hasManyRelations) {
|
||||
@@ -640,14 +636,14 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
relationTo: { type: String, enum: field.relationTo },
|
||||
value: {
|
||||
type: valueType,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
refPath: `${field.name}.${locale}.relationTo`,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
localeSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
type: valueType,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
ref: field.relationTo,
|
||||
}
|
||||
}
|
||||
@@ -668,7 +664,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
relationTo: { type: String, enum: field.relationTo },
|
||||
value: {
|
||||
type: valueType,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
refPath: `${field.name}.relationTo`,
|
||||
},
|
||||
}
|
||||
@@ -682,7 +678,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
} else {
|
||||
schemaToReturn = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
type: valueType,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
ref: field.relationTo,
|
||||
}
|
||||
|
||||
@@ -699,30 +695,3 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const getRelationshipValueType = (field: RelationshipField | UploadField, payload: Payload) => {
|
||||
if (typeof field.relationTo === 'string') {
|
||||
const { customIDType } = payload.collections[field.relationTo]
|
||||
|
||||
if (!customIDType) {
|
||||
return mongoose.Schema.Types.ObjectId
|
||||
}
|
||||
|
||||
if (customIDType === 'number') {
|
||||
return mongoose.Schema.Types.Number
|
||||
}
|
||||
|
||||
return mongoose.Schema.Types.String
|
||||
}
|
||||
|
||||
// has custom id relationTo
|
||||
if (
|
||||
field.relationTo.some((relationTo) => {
|
||||
return !!payload.collections[relationTo].customIDType
|
||||
})
|
||||
) {
|
||||
return mongoose.Schema.Types.Mixed
|
||||
}
|
||||
|
||||
return mongoose.Schema.Types.ObjectId
|
||||
}
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import type { ClientSession, Model } from 'mongoose'
|
||||
import type { Field, PayloadRequest, SanitizedConfig } from 'payload'
|
||||
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
import { sanitizeRelationshipIDs } from '../utilities/sanitizeRelationshipIDs.js'
|
||||
import { withSession } from '../withSession.js'
|
||||
|
||||
const migrateModelWithBatching = async ({
|
||||
batchSize,
|
||||
config,
|
||||
fields,
|
||||
Model,
|
||||
session,
|
||||
}: {
|
||||
batchSize: number
|
||||
config: SanitizedConfig
|
||||
fields: Field[]
|
||||
Model: Model<any>
|
||||
session: ClientSession
|
||||
}): Promise<void> => {
|
||||
let hasNext = true
|
||||
let skip = 0
|
||||
|
||||
while (hasNext) {
|
||||
const docs = await Model.find(
|
||||
{},
|
||||
{},
|
||||
{
|
||||
lean: true,
|
||||
limit: batchSize + 1,
|
||||
session,
|
||||
skip,
|
||||
},
|
||||
)
|
||||
|
||||
if (docs.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
hasNext = docs.length > batchSize
|
||||
|
||||
if (hasNext) {
|
||||
docs.pop()
|
||||
}
|
||||
|
||||
for (const doc of docs) {
|
||||
sanitizeRelationshipIDs({ config, data: doc, fields })
|
||||
}
|
||||
|
||||
await Model.collection.bulkWrite(
|
||||
docs.map((doc) => ({
|
||||
updateOne: {
|
||||
filter: { _id: doc._id },
|
||||
update: {
|
||||
$set: doc,
|
||||
},
|
||||
},
|
||||
})),
|
||||
{ session },
|
||||
)
|
||||
|
||||
skip += batchSize
|
||||
}
|
||||
}
|
||||
|
||||
const hasRelationshipOrUploadField = ({ fields }: { fields: Field[] }): boolean => {
|
||||
for (const field of fields) {
|
||||
if (field.type === 'relationship' || field.type === 'upload') {
|
||||
return true
|
||||
}
|
||||
|
||||
if ('fields' in field) {
|
||||
if (hasRelationshipOrUploadField({ fields: field.fields })) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if ('blocks' in field) {
|
||||
for (const block of field.blocks) {
|
||||
if (hasRelationshipOrUploadField({ fields: block.fields })) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ('tabs' in field) {
|
||||
for (const tab of field.tabs) {
|
||||
if (hasRelationshipOrUploadField({ fields: tab.fields })) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export async function migrateRelationshipsV2_V3({
|
||||
batchSize,
|
||||
req,
|
||||
}: {
|
||||
batchSize: number
|
||||
req: PayloadRequest
|
||||
}): Promise<void> {
|
||||
const { payload } = req
|
||||
const db = payload.db as MongooseAdapter
|
||||
const config = payload.config
|
||||
|
||||
const { session } = await withSession(db, req)
|
||||
|
||||
for (const collection of payload.config.collections.filter(hasRelationshipOrUploadField)) {
|
||||
payload.logger.info(`Migrating collection "${collection.slug}"`)
|
||||
|
||||
await migrateModelWithBatching({
|
||||
batchSize,
|
||||
config,
|
||||
fields: collection.fields,
|
||||
Model: db.collections[collection.slug],
|
||||
session,
|
||||
})
|
||||
|
||||
payload.logger.info(`Migrated collection "${collection.slug}"`)
|
||||
|
||||
if (collection.versions) {
|
||||
payload.logger.info(`Migrating collection versions "${collection.slug}"`)
|
||||
|
||||
await migrateModelWithBatching({
|
||||
batchSize,
|
||||
config,
|
||||
fields: buildVersionCollectionFields(config, collection),
|
||||
Model: db.versions[collection.slug],
|
||||
session,
|
||||
})
|
||||
|
||||
payload.logger.info(`Migrated collection versions "${collection.slug}"`)
|
||||
}
|
||||
}
|
||||
|
||||
const { globals: GlobalsModel } = db
|
||||
|
||||
for (const global of payload.config.globals.filter(hasRelationshipOrUploadField)) {
|
||||
payload.logger.info(`Migrating global "${global.slug}"`)
|
||||
|
||||
const doc = await GlobalsModel.findOne<Record<string, unknown>>(
|
||||
{
|
||||
globalType: {
|
||||
$eq: global.slug,
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ lean: true, session },
|
||||
)
|
||||
|
||||
sanitizeRelationshipIDs({ config, data: doc, fields: global.fields })
|
||||
|
||||
await GlobalsModel.collection.updateOne(
|
||||
{
|
||||
globalType: global.slug,
|
||||
},
|
||||
{ $set: doc },
|
||||
{ session },
|
||||
)
|
||||
|
||||
payload.logger.info(`Migrated global "${global.slug}"`)
|
||||
|
||||
if (global.versions) {
|
||||
payload.logger.info(`Migrating global versions "${global.slug}"`)
|
||||
|
||||
await migrateModelWithBatching({
|
||||
batchSize,
|
||||
config,
|
||||
fields: buildVersionGlobalFields(config, global),
|
||||
Model: db.versions[global.slug],
|
||||
session,
|
||||
})
|
||||
|
||||
payload.logger.info(`Migrated global versions "${global.slug}"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import type { ClientSession } from 'mongoose'
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
import { withSession } from '../withSession.js'
|
||||
|
||||
export async function migrateVersionsV1_V2({ req }: { req: PayloadRequest }) {
|
||||
const { payload } = req
|
||||
|
||||
const { session } = await withSession(payload.db as MongooseAdapter, req)
|
||||
|
||||
// For each collection
|
||||
|
||||
for (const { slug, versions } of payload.config.collections) {
|
||||
if (versions?.drafts) {
|
||||
await migrateCollectionDocs({ slug, payload, session })
|
||||
|
||||
payload.logger.info(`Migrated the "${slug}" collection.`)
|
||||
}
|
||||
}
|
||||
|
||||
// For each global
|
||||
for (const { slug, versions } of payload.config.globals) {
|
||||
if (versions) {
|
||||
const VersionsModel = payload.db.versions[slug]
|
||||
|
||||
await VersionsModel.findOneAndUpdate(
|
||||
{},
|
||||
{ latest: true },
|
||||
{
|
||||
session,
|
||||
sort: { updatedAt: -1 },
|
||||
},
|
||||
).exec()
|
||||
|
||||
payload.logger.info(`Migrated the "${slug}" global.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateCollectionDocs({
|
||||
slug,
|
||||
docsAtATime = 100,
|
||||
payload,
|
||||
session,
|
||||
}: {
|
||||
docsAtATime?: number
|
||||
payload: Payload
|
||||
session: ClientSession
|
||||
slug: string
|
||||
}) {
|
||||
const VersionsModel = payload.db.versions[slug]
|
||||
const remainingDocs = await VersionsModel.aggregate(
|
||||
[
|
||||
// Sort so that newest are first
|
||||
{
|
||||
$sort: {
|
||||
updatedAt: -1,
|
||||
},
|
||||
},
|
||||
// Group by parent ID
|
||||
// take the $first of each
|
||||
{
|
||||
$group: {
|
||||
_id: '$parent',
|
||||
_versionID: { $first: '$_id' },
|
||||
createdAt: { $first: '$createdAt' },
|
||||
latest: { $first: '$latest' },
|
||||
updatedAt: { $first: '$updatedAt' },
|
||||
version: { $first: '$version' },
|
||||
},
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
latest: { $eq: null },
|
||||
},
|
||||
},
|
||||
{
|
||||
$limit: docsAtATime,
|
||||
},
|
||||
],
|
||||
{
|
||||
allowDiskUse: true,
|
||||
session,
|
||||
},
|
||||
).exec()
|
||||
|
||||
if (!remainingDocs || remainingDocs.length === 0) {
|
||||
const newVersions = await VersionsModel.find(
|
||||
{
|
||||
latest: {
|
||||
$eq: true,
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
{ session },
|
||||
)
|
||||
|
||||
if (newVersions?.length) {
|
||||
payload.logger.info(
|
||||
`Migrated ${newVersions.length} documents in the "${slug}" versions collection.`,
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const remainingDocIds = remainingDocs.map((doc) => doc._versionID)
|
||||
|
||||
await VersionsModel.updateMany(
|
||||
{
|
||||
_id: {
|
||||
$in: remainingDocIds,
|
||||
},
|
||||
},
|
||||
{
|
||||
latest: true,
|
||||
},
|
||||
{
|
||||
session,
|
||||
},
|
||||
)
|
||||
|
||||
await migrateCollectionDocs({ slug, payload, session })
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
const imports = `import { migrateRelationshipsV2_V3 } from '@payloadcms/db-mongodb/migration-utils'`
|
||||
const upSQL = ` await migrateRelationshipsV2_V3({
|
||||
batchSize: 100,
|
||||
req,
|
||||
})
|
||||
`
|
||||
export { imports, upSQL }
|
||||
@@ -0,0 +1,96 @@
|
||||
module.exports.up = ` async function migrateCollectionDocs(slug: string, docsAtATime = 100) {
|
||||
const VersionsModel = payload.db.versions[slug]
|
||||
const remainingDocs = await VersionsModel.aggregate(
|
||||
[
|
||||
// Sort so that newest are first
|
||||
{
|
||||
$sort: {
|
||||
updatedAt: -1,
|
||||
},
|
||||
},
|
||||
// Group by parent ID
|
||||
// take the $first of each
|
||||
{
|
||||
$group: {
|
||||
_id: '$parent',
|
||||
_versionID: { $first: '$_id' },
|
||||
createdAt: { $first: '$createdAt' },
|
||||
latest: { $first: '$latest' },
|
||||
updatedAt: { $first: '$updatedAt' },
|
||||
version: { $first: '$version' },
|
||||
},
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
latest: { $eq: null },
|
||||
},
|
||||
},
|
||||
{
|
||||
$limit: docsAtATime,
|
||||
},
|
||||
],
|
||||
{
|
||||
allowDiskUse: true,
|
||||
},
|
||||
).exec()
|
||||
|
||||
if (!remainingDocs || remainingDocs.length === 0) {
|
||||
const newVersions = await VersionsModel.find({
|
||||
latest: {
|
||||
$eq: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (newVersions?.length) {
|
||||
payload.logger.info(
|
||||
\`Migrated \${newVersions.length} documents in the "\${slug}" versions collection.\`,
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const remainingDocIds = remainingDocs.map((doc) => doc._versionID)
|
||||
|
||||
await VersionsModel.updateMany(
|
||||
{
|
||||
_id: {
|
||||
$in: remainingDocIds,
|
||||
},
|
||||
},
|
||||
{
|
||||
latest: true,
|
||||
},
|
||||
)
|
||||
|
||||
await migrateCollectionDocs(slug)
|
||||
}
|
||||
|
||||
// For each collection
|
||||
await Promise.all(
|
||||
payload.config.collections.map(async ({ slug, versions }) => {
|
||||
if (versions?.drafts) {
|
||||
return migrateCollectionDocs(slug)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
// For each global
|
||||
await Promise.all(
|
||||
payload.config.globals.map(async ({ slug, versions }) => {
|
||||
if (versions) {
|
||||
const VersionsModel = payload.db.versions[slug]
|
||||
|
||||
await VersionsModel.findOneAndUpdate(
|
||||
{},
|
||||
{ latest: true },
|
||||
{
|
||||
sort: { updatedAt: -1 },
|
||||
},
|
||||
).exec()
|
||||
|
||||
payload.logger.info(\`Migrated the "\${slug}" global.\`)
|
||||
}
|
||||
}),
|
||||
)
|
||||
`
|
||||
@@ -1,6 +0,0 @@
|
||||
const imports = `import { migrateVersionsV1_V2 } from '@payloadcms/db-mongodb/migration-utils'`
|
||||
const upSQL = ` await migrateVersionsV1_V2({
|
||||
req,
|
||||
})
|
||||
`
|
||||
export { imports, upSQL }
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Field, Operator, PathToQuery, Payload } from 'payload'
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
import ObjectIdImport from 'bson-objectid'
|
||||
import mongoose from 'mongoose'
|
||||
import { getLocalizedPaths } from 'payload'
|
||||
import { validOperators } from 'payload/shared'
|
||||
|
||||
@@ -9,6 +10,9 @@ import type { MongooseAdapter } from '../index.js'
|
||||
import { operatorMap } from './operatorMap.js'
|
||||
import { sanitizeQueryValue } from './sanitizeQueryValue.js'
|
||||
|
||||
const ObjectId = (ObjectIdImport.default ||
|
||||
ObjectIdImport) as unknown as typeof ObjectIdImport.default
|
||||
|
||||
type SearchParam = {
|
||||
path?: string
|
||||
rawQuery?: unknown
|
||||
@@ -83,13 +87,13 @@ export async function buildSearchParam({
|
||||
}
|
||||
|
||||
const [{ field, path }] = paths
|
||||
|
||||
if (path) {
|
||||
const sanitizedQueryValue = sanitizeQueryValue({
|
||||
field,
|
||||
hasCustomID,
|
||||
operator,
|
||||
path,
|
||||
payload,
|
||||
val,
|
||||
})
|
||||
|
||||
@@ -141,7 +145,7 @@ export async function buildSearchParam({
|
||||
const stringID = doc._id.toString()
|
||||
$in.push(stringID)
|
||||
|
||||
if (Types.ObjectId.isValid(stringID)) {
|
||||
if (mongoose.Types.ObjectId.isValid(stringID)) {
|
||||
$in.push(doc._id)
|
||||
}
|
||||
})
|
||||
@@ -203,9 +207,9 @@ export async function buildSearchParam({
|
||||
}
|
||||
|
||||
if (typeof formattedValue === 'string') {
|
||||
if (Types.ObjectId.isValid(formattedValue)) {
|
||||
if (mongoose.Types.ObjectId.isValid(formattedValue)) {
|
||||
result.value[multiIDCondition].push({
|
||||
[path]: { [operatorKey]: new Types.ObjectId(formattedValue) },
|
||||
[path]: { [operatorKey]: ObjectId(formattedValue) },
|
||||
})
|
||||
} else {
|
||||
;(Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]).forEach(
|
||||
|
||||
@@ -71,10 +71,7 @@ export async function parseParams({
|
||||
[searchParam.path]: searchParam.value,
|
||||
}
|
||||
} else if (typeof searchParam?.value === 'object') {
|
||||
result = deepMergeWithCombinedArrays(result, searchParam.value, {
|
||||
// dont clone Types.ObjectIDs
|
||||
clone: false,
|
||||
})
|
||||
result = deepMergeWithCombinedArrays(result, searchParam.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import type { Block, Field, Payload, RelationshipField, TabAsField } from 'payload'
|
||||
import type { Field, TabAsField } from 'payload'
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
import { createArrayFromCommaDelineated, flattenTopLevelFields } from 'payload'
|
||||
import ObjectIdImport from 'bson-objectid'
|
||||
import mongoose from 'mongoose'
|
||||
import { createArrayFromCommaDelineated } from 'payload'
|
||||
|
||||
type SanitizeQueryValueArgs = {
|
||||
field: Field | TabAsField
|
||||
hasCustomID: boolean
|
||||
operator: string
|
||||
path: string
|
||||
payload: Payload
|
||||
val: any
|
||||
}
|
||||
|
||||
const buildExistsQuery = (formattedValue, path, treatEmptyString = true) => {
|
||||
const buildExistsQuery = (formattedValue, path) => {
|
||||
if (formattedValue) {
|
||||
return {
|
||||
rawQuery: {
|
||||
$and: [
|
||||
{ [path]: { $exists: true } },
|
||||
{ [path]: { $ne: null } },
|
||||
...(treatEmptyString ? [{ [path]: { $ne: '' } }] : []), // Treat empty string as null / undefined
|
||||
{ [path]: { $ne: '' } }, // Exclude null and empty string
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -29,56 +29,20 @@ const buildExistsQuery = (formattedValue, path, treatEmptyString = true) => {
|
||||
$or: [
|
||||
{ [path]: { $exists: false } },
|
||||
{ [path]: { $eq: null } },
|
||||
...(treatEmptyString ? [{ [path]: { $eq: '' } }] : []), // Treat empty string as null / undefined
|
||||
{ [path]: { $eq: '' } }, // Treat empty string as null / undefined
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns nestedField Field object from blocks.nestedField path because getLocalizedPaths splits them only for relationships
|
||||
const getFieldFromSegments = ({
|
||||
field,
|
||||
segments,
|
||||
}: {
|
||||
field: Block | Field | TabAsField
|
||||
segments: string[]
|
||||
}) => {
|
||||
if ('blocks' in field) {
|
||||
for (const block of field.blocks) {
|
||||
const field = getFieldFromSegments({ field: block, segments })
|
||||
if (field) {
|
||||
return field
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ('fields' in field) {
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const foundField = flattenTopLevelFields(field.fields).find(
|
||||
(each) => each.name === segments[i],
|
||||
)
|
||||
|
||||
if (!foundField) {
|
||||
break
|
||||
}
|
||||
|
||||
if (foundField && segments.length - 1 === i) {
|
||||
return foundField
|
||||
}
|
||||
|
||||
segments.shift()
|
||||
return getFieldFromSegments({ field: foundField, segments })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ObjectId = (ObjectIdImport.default ||
|
||||
ObjectIdImport) as unknown as typeof ObjectIdImport.default
|
||||
export const sanitizeQueryValue = ({
|
||||
field,
|
||||
hasCustomID,
|
||||
operator,
|
||||
path,
|
||||
payload,
|
||||
val,
|
||||
}: SanitizeQueryValueArgs): {
|
||||
operator?: string
|
||||
@@ -88,31 +52,21 @@ export const sanitizeQueryValue = ({
|
||||
let formattedValue = val
|
||||
let formattedOperator = operator
|
||||
|
||||
if (['array', 'blocks', 'group', 'tab'].includes(field.type) && path.includes('.')) {
|
||||
const segments = path.split('.')
|
||||
segments.shift()
|
||||
const foundField = getFieldFromSegments({ field, segments })
|
||||
|
||||
if (foundField) {
|
||||
field = foundField
|
||||
}
|
||||
}
|
||||
|
||||
// Disregard invalid _ids
|
||||
if (path === '_id') {
|
||||
if (typeof val === 'string' && val.split(',').length === 1) {
|
||||
if (!hasCustomID) {
|
||||
const isValid = Types.ObjectId.isValid(val)
|
||||
const isValid = mongoose.Types.ObjectId.isValid(val)
|
||||
|
||||
if (!isValid) {
|
||||
return { operator: formattedOperator, val: undefined }
|
||||
} else {
|
||||
if (['in', 'not_in'].includes(operator)) {
|
||||
formattedValue = createArrayFromCommaDelineated(formattedValue).map(
|
||||
(id) => new Types.ObjectId(id),
|
||||
formattedValue = createArrayFromCommaDelineated(formattedValue).map((id) =>
|
||||
ObjectId(id),
|
||||
)
|
||||
} else {
|
||||
formattedValue = new Types.ObjectId(val)
|
||||
formattedValue = ObjectId(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,22 +84,21 @@ export const sanitizeQueryValue = ({
|
||||
}
|
||||
|
||||
formattedValue = formattedValue.reduce((formattedValues, inVal) => {
|
||||
const newValues = [inVal]
|
||||
if (!hasCustomID) {
|
||||
if (Types.ObjectId.isValid(inVal)) {
|
||||
formattedValues.push(new Types.ObjectId(inVal))
|
||||
if (mongoose.Types.ObjectId.isValid(inVal)) {
|
||||
newValues.push(ObjectId(inVal))
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === 'number') {
|
||||
const parsedNumber = parseFloat(inVal)
|
||||
if (!Number.isNaN(parsedNumber)) {
|
||||
formattedValues.push(parsedNumber)
|
||||
newValues.push(parsedNumber)
|
||||
}
|
||||
} else {
|
||||
formattedValues.push(inVal)
|
||||
}
|
||||
|
||||
return formattedValues
|
||||
return [...formattedValues, ...newValues]
|
||||
}, [])
|
||||
}
|
||||
}
|
||||
@@ -201,10 +154,10 @@ export const sanitizeQueryValue = ({
|
||||
formattedValue.relationTo
|
||||
) {
|
||||
const { value } = formattedValue
|
||||
const isValid = Types.ObjectId.isValid(value)
|
||||
const isValid = mongoose.Types.ObjectId.isValid(value)
|
||||
|
||||
if (isValid) {
|
||||
formattedValue.value = new Types.ObjectId(value)
|
||||
formattedValue.value = ObjectId(value)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -217,88 +170,25 @@ export const sanitizeQueryValue = ({
|
||||
}
|
||||
}
|
||||
|
||||
const relationTo = (field as RelationshipField).relationTo
|
||||
|
||||
if (['in', 'not_in'].includes(operator) && Array.isArray(formattedValue)) {
|
||||
formattedValue = formattedValue.reduce((formattedValues, inVal) => {
|
||||
if (!inVal) {
|
||||
return formattedValues
|
||||
const newValues = [inVal]
|
||||
if (mongoose.Types.ObjectId.isValid(inVal)) {
|
||||
newValues.push(ObjectId(inVal))
|
||||
}
|
||||
|
||||
if (typeof relationTo === 'string' && payload.collections[relationTo].customIDType) {
|
||||
if (payload.collections[relationTo].customIDType === 'number') {
|
||||
const parsedNumber = parseFloat(inVal)
|
||||
if (!Number.isNaN(parsedNumber)) {
|
||||
formattedValues.push(parsedNumber)
|
||||
return formattedValues
|
||||
}
|
||||
}
|
||||
|
||||
formattedValues.push(inVal)
|
||||
return formattedValues
|
||||
const parsedNumber = parseFloat(inVal)
|
||||
if (!Number.isNaN(parsedNumber)) {
|
||||
newValues.push(parsedNumber)
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(relationTo) &&
|
||||
relationTo.some((relationTo) => !!payload.collections[relationTo].customIDType)
|
||||
) {
|
||||
if (Types.ObjectId.isValid(inVal.toString())) {
|
||||
formattedValues.push(new Types.ObjectId(inVal))
|
||||
} else {
|
||||
formattedValues.push(inVal)
|
||||
}
|
||||
return formattedValues
|
||||
}
|
||||
|
||||
if (Types.ObjectId.isValid(inVal.toString())) {
|
||||
formattedValues.push(new Types.ObjectId(inVal))
|
||||
}
|
||||
|
||||
return formattedValues
|
||||
return [...formattedValues, ...newValues]
|
||||
}, [])
|
||||
}
|
||||
|
||||
if (
|
||||
['contains', 'equals', 'like', 'not_equals'].includes(operator) &&
|
||||
(!Array.isArray(relationTo) || !path.endsWith('.relationTo'))
|
||||
) {
|
||||
if (typeof relationTo === 'string') {
|
||||
const customIDType = payload.collections[relationTo].customIDType
|
||||
|
||||
if (customIDType) {
|
||||
if (customIDType === 'number') {
|
||||
formattedValue = parseFloat(val)
|
||||
|
||||
if (Number.isNaN(formattedValue)) {
|
||||
return { operator: formattedOperator, val: undefined }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!Types.ObjectId.isValid(formattedValue)) {
|
||||
return { operator: formattedOperator, val: undefined }
|
||||
}
|
||||
formattedValue = new Types.ObjectId(formattedValue)
|
||||
}
|
||||
} else {
|
||||
const hasCustomIDType = relationTo.some(
|
||||
(relationTo) => !!payload.collections[relationTo].customIDType,
|
||||
)
|
||||
|
||||
if (hasCustomIDType) {
|
||||
if (typeof val === 'string') {
|
||||
const formattedNumber = Number(val)
|
||||
formattedValue = [Types.ObjectId.isValid(val) ? new Types.ObjectId(val) : val]
|
||||
formattedOperator = operator === 'not_equals' ? 'not_in' : 'in'
|
||||
if (!Number.isNaN(formattedNumber)) {
|
||||
formattedValue.push(formattedNumber)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!Types.ObjectId.isValid(formattedValue)) {
|
||||
return { operator: formattedOperator, val: undefined }
|
||||
}
|
||||
formattedValue = new Types.ObjectId(formattedValue)
|
||||
}
|
||||
if (operator === 'contains' && typeof formattedValue === 'string') {
|
||||
if (mongoose.Types.ObjectId.isValid(formattedValue)) {
|
||||
formattedValue = ObjectId(formattedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,7 +232,7 @@ export const sanitizeQueryValue = ({
|
||||
}
|
||||
|
||||
if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) {
|
||||
if (operator === 'contains' && !Types.ObjectId.isValid(formattedValue)) {
|
||||
if (operator === 'contains' && !mongoose.Types.ObjectId.isValid(formattedValue)) {
|
||||
formattedValue = {
|
||||
$options: 'i',
|
||||
$regex: formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||
@@ -352,12 +242,7 @@ export const sanitizeQueryValue = ({
|
||||
if (operator === 'exists') {
|
||||
formattedValue = formattedValue === 'true' || formattedValue === true
|
||||
|
||||
// _id can't be empty string, will error Cast to ObjectId failed for value ""
|
||||
return buildExistsQuery(
|
||||
formattedValue,
|
||||
path,
|
||||
!['relationship', 'upload'].includes(field.type),
|
||||
)
|
||||
return buildExistsQuery(formattedValue, path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
const useEstimatedCount =
|
||||
hasNearConstraint || !versionQuery || Object.keys(versionQuery).length === 0
|
||||
const paginationOptions: PaginateOptions = {
|
||||
forceCountFn: hasNearConstraint,
|
||||
lean: true,
|
||||
leanWithId: true,
|
||||
options,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { PayloadRequest, UpdateGlobal } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -10,13 +9,12 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const updateGlobal: UpdateGlobal = async function updateGlobal(
|
||||
this: MongooseAdapter,
|
||||
{ slug, data, options: optionsArgs = {}, req = {} as PayloadRequest, select },
|
||||
{ slug, data, req = {} as PayloadRequest, select },
|
||||
) {
|
||||
const Model = this.globals
|
||||
const fields = this.payload.config.globals.find((global) => global.slug === slug).fields
|
||||
|
||||
const options: QueryOptions = {
|
||||
...optionsArgs,
|
||||
const options = {
|
||||
...(await withSession(this, req)),
|
||||
lean: true,
|
||||
new: true,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
|
||||
import {
|
||||
buildVersionGlobalFields,
|
||||
type PayloadRequest,
|
||||
@@ -19,7 +17,6 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
id,
|
||||
global: globalSlug,
|
||||
locale,
|
||||
options: optionsArgs = {},
|
||||
req = {} as PayloadRequest,
|
||||
select,
|
||||
versionData,
|
||||
@@ -33,8 +30,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
||||
this.payload.config.globals.find((global) => global.slug === globalSlug),
|
||||
)
|
||||
|
||||
const options: QueryOptions = {
|
||||
...optionsArgs,
|
||||
const options = {
|
||||
...(await withSession(this, req)),
|
||||
lean: true,
|
||||
new: true,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
|
||||
import { buildVersionCollectionFields, type PayloadRequest, type UpdateVersion } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
@@ -10,16 +8,7 @@ import { withSession } from './withSession.js'
|
||||
|
||||
export const updateVersion: UpdateVersion = async function updateVersion(
|
||||
this: MongooseAdapter,
|
||||
{
|
||||
id,
|
||||
collection,
|
||||
locale,
|
||||
options: optionsArgs = {},
|
||||
req = {} as PayloadRequest,
|
||||
select,
|
||||
versionData,
|
||||
where,
|
||||
},
|
||||
{ id, collection, locale, req = {} as PayloadRequest, select, versionData, where },
|
||||
) {
|
||||
const VersionModel = this.versions[collection]
|
||||
const whereToUse = where || { id: { equals: id } }
|
||||
@@ -28,8 +17,7 @@ export const updateVersion: UpdateVersion = async function updateVersion(
|
||||
this.payload.collections[collection].config,
|
||||
)
|
||||
|
||||
const options: QueryOptions = {
|
||||
...optionsArgs,
|
||||
const options = {
|
||||
...(await withSession(this, req)),
|
||||
lean: true,
|
||||
new: true,
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
import type { Field, SanitizedConfig } from 'payload'
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
|
||||
import { sanitizeRelationshipIDs } from './sanitizeRelationshipIDs.js'
|
||||
|
||||
const flattenRelationshipValues = (obj: Record<string, any>, prefix = ''): Record<string, any> => {
|
||||
return Object.keys(obj).reduce(
|
||||
(acc, key) => {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key
|
||||
const value = obj[key]
|
||||
|
||||
if (value && typeof value === 'object' && !(value instanceof Types.ObjectId)) {
|
||||
Object.assign(acc, flattenRelationshipValues(value, fullKey))
|
||||
// skip relationTo and blockType
|
||||
} else if (!fullKey.endsWith('relationTo') && !fullKey.endsWith('blockType')) {
|
||||
acc[fullKey] = value
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
)
|
||||
}
|
||||
|
||||
const relsFields: Field[] = [
|
||||
{
|
||||
name: 'rel_1',
|
||||
type: 'relationship',
|
||||
relationTo: 'rels',
|
||||
},
|
||||
{
|
||||
name: 'rel_1_l',
|
||||
type: 'relationship',
|
||||
localized: true,
|
||||
relationTo: 'rels',
|
||||
},
|
||||
{
|
||||
name: 'rel_2',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
relationTo: 'rels',
|
||||
},
|
||||
{
|
||||
name: 'rel_2_l',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
localized: true,
|
||||
relationTo: 'rels',
|
||||
},
|
||||
{
|
||||
name: 'rel_3',
|
||||
type: 'relationship',
|
||||
relationTo: ['rels'],
|
||||
},
|
||||
{
|
||||
name: 'rel_3_l',
|
||||
type: 'relationship',
|
||||
localized: true,
|
||||
relationTo: ['rels'],
|
||||
},
|
||||
{
|
||||
name: 'rel_4',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
relationTo: ['rels'],
|
||||
},
|
||||
{
|
||||
name: 'rel_4_l',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
localized: true,
|
||||
relationTo: ['rels'],
|
||||
},
|
||||
]
|
||||
|
||||
const config = {
|
||||
collections: [
|
||||
{
|
||||
slug: 'docs',
|
||||
fields: [
|
||||
...relsFields,
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: relsFields,
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [{ slug: 'block', fields: relsFields }],
|
||||
},
|
||||
...relsFields,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'arrayLocalized',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: relsFields,
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [{ slug: 'block', fields: relsFields }],
|
||||
},
|
||||
...relsFields,
|
||||
],
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
...relsFields,
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: relsFields,
|
||||
},
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: relsFields,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
...relsFields,
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: relsFields,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'groupLocalized',
|
||||
type: 'group',
|
||||
fields: [
|
||||
...relsFields,
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: relsFields,
|
||||
},
|
||||
],
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'groupAndRow',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
...relsFields,
|
||||
{
|
||||
type: 'array',
|
||||
name: 'array',
|
||||
fields: relsFields,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab',
|
||||
fields: relsFields,
|
||||
},
|
||||
{
|
||||
name: 'tabLocalized',
|
||||
fields: relsFields,
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'rels',
|
||||
fields: [],
|
||||
},
|
||||
],
|
||||
localization: {
|
||||
defaultLocale: 'en',
|
||||
localeCodes: ['en', 'es'],
|
||||
locales: [
|
||||
{ code: 'en', label: 'EN' },
|
||||
{ code: 'es', label: 'ES' },
|
||||
],
|
||||
},
|
||||
} as SanitizedConfig
|
||||
|
||||
const relsData = {
|
||||
rel_1: new Types.ObjectId().toHexString(),
|
||||
rel_1_l: {
|
||||
en: new Types.ObjectId().toHexString(),
|
||||
es: new Types.ObjectId().toHexString(),
|
||||
},
|
||||
rel_2: [new Types.ObjectId().toHexString()],
|
||||
rel_2_l: {
|
||||
en: [new Types.ObjectId().toHexString()],
|
||||
es: [new Types.ObjectId().toHexString()],
|
||||
},
|
||||
rel_3: {
|
||||
relationTo: 'rels',
|
||||
value: new Types.ObjectId().toHexString(),
|
||||
},
|
||||
rel_3_l: {
|
||||
en: {
|
||||
relationTo: 'rels',
|
||||
value: new Types.ObjectId().toHexString(),
|
||||
},
|
||||
es: {
|
||||
relationTo: 'rels',
|
||||
value: new Types.ObjectId().toHexString(),
|
||||
},
|
||||
},
|
||||
rel_4: [
|
||||
{
|
||||
relationTo: 'rels',
|
||||
value: new Types.ObjectId().toHexString(),
|
||||
},
|
||||
],
|
||||
rel_4_l: {
|
||||
en: [
|
||||
{
|
||||
relationTo: 'rels',
|
||||
value: new Types.ObjectId().toHexString(),
|
||||
},
|
||||
],
|
||||
es: [
|
||||
{
|
||||
relationTo: 'rels',
|
||||
value: new Types.ObjectId().toHexString(),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
describe('sanitizeRelationshipIDs', () => {
|
||||
it('should sanitize relationships', () => {
|
||||
const data = {
|
||||
...relsData,
|
||||
array: [
|
||||
{
|
||||
...relsData,
|
||||
array: [{ ...relsData }],
|
||||
blocks: [
|
||||
{
|
||||
blockType: 'block',
|
||||
...relsData,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
arrayLocalized: {
|
||||
en: [
|
||||
{
|
||||
...relsData,
|
||||
array: [{ ...relsData }],
|
||||
blocks: [
|
||||
{
|
||||
blockType: 'block',
|
||||
...relsData,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
es: [
|
||||
{
|
||||
...relsData,
|
||||
array: [{ ...relsData }],
|
||||
blocks: [
|
||||
{
|
||||
blockType: 'block',
|
||||
...relsData,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
blockType: 'block',
|
||||
...relsData,
|
||||
array: [{ ...relsData }],
|
||||
group: { ...relsData },
|
||||
},
|
||||
],
|
||||
group: {
|
||||
...relsData,
|
||||
array: [{ ...relsData }],
|
||||
},
|
||||
groupAndRow: {
|
||||
...relsData,
|
||||
array: [{ ...relsData }],
|
||||
},
|
||||
groupLocalized: {
|
||||
en: {
|
||||
...relsData,
|
||||
array: [{ ...relsData }],
|
||||
},
|
||||
es: {
|
||||
...relsData,
|
||||
array: [{ ...relsData }],
|
||||
},
|
||||
},
|
||||
tab: { ...relsData },
|
||||
tabLocalized: {
|
||||
en: { ...relsData },
|
||||
es: { ...relsData },
|
||||
},
|
||||
}
|
||||
const flattenValuesBefore = Object.values(flattenRelationshipValues(data))
|
||||
|
||||
sanitizeRelationshipIDs({ config, data, fields: config.collections[0].fields })
|
||||
const flattenValuesAfter = Object.values(flattenRelationshipValues(data))
|
||||
|
||||
flattenValuesAfter.forEach((value, i) => {
|
||||
expect(value).toBeInstanceOf(Types.ObjectId)
|
||||
expect(flattenValuesBefore[i]).toBe(value.toHexString())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig, Field, SanitizedConfig, TraverseFieldsCallback } from 'payload'
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
import mongoose from 'mongoose'
|
||||
import { APIError, traverseFields } from 'payload'
|
||||
import { fieldAffectsData } from 'payload/shared'
|
||||
|
||||
@@ -25,14 +25,14 @@ const convertValue = ({
|
||||
}: {
|
||||
relatedCollection: CollectionConfig
|
||||
value: number | string
|
||||
}): number | string | Types.ObjectId => {
|
||||
}): mongoose.Types.ObjectId | number | string => {
|
||||
const customIDField = relatedCollection.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
|
||||
if (!customIDField) {
|
||||
try {
|
||||
return new Types.ObjectId(value)
|
||||
return new mongoose.Types.ObjectId(value)
|
||||
} catch (error) {
|
||||
throw new APIError(
|
||||
`Failed to create ObjectId from value: ${value}. Error: ${error.message}`,
|
||||
@@ -141,7 +141,7 @@ export const sanitizeRelationshipIDs = ({
|
||||
}
|
||||
}
|
||||
|
||||
traverseFields({ callback: sanitize, fields, fillEmpty: false, ref: data })
|
||||
traverseFields({ callback: sanitize, fields, ref: data })
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type {
|
||||
Payload,
|
||||
Permissions,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedGlobalConfig,
|
||||
SanitizedPermissions,
|
||||
} from 'payload'
|
||||
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
@@ -23,7 +23,7 @@ export const DocumentTabs: React.FC<{
|
||||
globalConfig: SanitizedGlobalConfig
|
||||
i18n: I18n
|
||||
payload: Payload
|
||||
permissions: SanitizedPermissions
|
||||
permissions: Permissions
|
||||
}> = (props) => {
|
||||
const { collectionConfig, globalConfig, i18n, payload, permissions } = props
|
||||
const { config } = payload
|
||||
|
||||
@@ -72,8 +72,9 @@ export const tabs: Record<
|
||||
condition: ({ collectionConfig, globalConfig, permissions }) =>
|
||||
Boolean(
|
||||
(collectionConfig?.versions &&
|
||||
permissions?.collections?.[collectionConfig?.slug]?.readVersions) ||
|
||||
(globalConfig?.versions && permissions?.globals?.[globalConfig?.slug]?.readVersions),
|
||||
permissions?.collections?.[collectionConfig?.slug]?.readVersions?.permission) ||
|
||||
(globalConfig?.versions &&
|
||||
permissions?.globals?.[globalConfig?.slug]?.readVersions?.permission),
|
||||
),
|
||||
href: '/versions',
|
||||
label: ({ t }) => t('version:versions'),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type {
|
||||
Payload,
|
||||
Permissions,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedGlobalConfig,
|
||||
SanitizedPermissions,
|
||||
} from 'payload'
|
||||
|
||||
import { Gutter, RenderTitle } from '@payloadcms/ui'
|
||||
@@ -20,7 +20,7 @@ export const DocumentHeader: React.FC<{
|
||||
hideTabs?: boolean
|
||||
i18n: I18n
|
||||
payload: Payload
|
||||
permissions: SanitizedPermissions
|
||||
permissions: Permissions
|
||||
}> = (props) => {
|
||||
const { collectionConfig, globalConfig, hideTabs, i18n, payload, permissions } = props
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { I18n, I18nClient } from '@payloadcms/translations'
|
||||
import type { PayloadRequest, SanitizedConfig, SanitizedPermissions, User } from 'payload'
|
||||
import type { PayloadRequest, Permissions, SanitizedConfig, User } from 'payload'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
@@ -11,7 +11,7 @@ import { getRequestLanguage } from './getRequestLanguage.js'
|
||||
|
||||
type Result = {
|
||||
i18n: I18nClient
|
||||
permissions: SanitizedPermissions
|
||||
permissions: Permissions
|
||||
req: PayloadRequest
|
||||
user: User
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import type { FormProps, UserWithToken } from '@payloadcms/ui'
|
||||
import type {
|
||||
ClientCollectionConfig,
|
||||
DocumentPermissions,
|
||||
DocumentPreferences,
|
||||
FormState,
|
||||
LoginWithUsernameOptions,
|
||||
SanitizedDocumentPermissions,
|
||||
} from 'payload'
|
||||
|
||||
import {
|
||||
@@ -24,7 +24,7 @@ import { abortAndIgnore } from '@payloadcms/ui/shared'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
export const CreateFirstUserClient: React.FC<{
|
||||
docPermissions: SanitizedDocumentPermissions
|
||||
docPermissions: DocumentPermissions
|
||||
docPreferences: DocumentPreferences
|
||||
initialState: FormState
|
||||
loginWithUsername?: false | LoginWithUsernameOptions
|
||||
@@ -114,7 +114,7 @@ export const CreateFirstUserClient: React.FC<{
|
||||
parentIndexPath=""
|
||||
parentPath=""
|
||||
parentSchemaPath={userSlug}
|
||||
permissions={true}
|
||||
permissions={null}
|
||||
readOnly={false}
|
||||
/>
|
||||
<FormSubmit size="large">{t('general:create')}</FormSubmit>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { groupNavItems } from '@payloadcms/ui/shared'
|
||||
import type { ClientUser, SanitizedPermissions, ServerProps, VisibleEntities } from 'payload'
|
||||
import type { ClientUser, Permissions, ServerProps, VisibleEntities } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { Button, Card, Gutter, Locked } from '@payloadcms/ui'
|
||||
@@ -19,7 +19,7 @@ export type DashboardProps = {
|
||||
}>
|
||||
Link: React.ComponentType<any>
|
||||
navGroups?: ReturnType<typeof groupNavItems>
|
||||
permissions: SanitizedPermissions
|
||||
permissions: Permissions
|
||||
visibleEntities: VisibleEntities
|
||||
} & ServerProps
|
||||
|
||||
@@ -94,7 +94,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
path: `/collections/${slug}/create`,
|
||||
})
|
||||
|
||||
hasCreatePermission = permissions?.collections?.[slug]?.create
|
||||
hasCreatePermission = permissions?.collections?.[slug]?.create?.permission
|
||||
}
|
||||
|
||||
if (type === EntityType.global) {
|
||||
|
||||
@@ -35,13 +35,14 @@ export const Dashboard: React.FC<AdminViewProps> = async ({
|
||||
|
||||
const collections = config.collections.filter(
|
||||
(collection) =>
|
||||
permissions?.collections?.[collection.slug]?.read &&
|
||||
permissions?.collections?.[collection.slug]?.read?.permission &&
|
||||
visibleEntities.collections.includes(collection.slug),
|
||||
)
|
||||
|
||||
const globals = config.globals.filter(
|
||||
(global) =>
|
||||
permissions?.globals?.[global.slug]?.read && visibleEntities.globals.includes(global.slug),
|
||||
permissions?.globals?.[global.slug]?.read?.permission &&
|
||||
visibleEntities.globals.includes(global.slug),
|
||||
)
|
||||
|
||||
// Query locked global documents only if there are globals in the config
|
||||
|
||||
@@ -3,7 +3,6 @@ import type {
|
||||
DocumentPermissions,
|
||||
PayloadRequest,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedDocumentPermissions,
|
||||
SanitizedGlobalConfig,
|
||||
} from 'payload'
|
||||
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
hasSavePermission as getHasSavePermission,
|
||||
isEditing as getIsEditing,
|
||||
} from '@payloadcms/ui/shared'
|
||||
import { docAccessOperation, docAccessOperationGlobal, sanitizePermissions } from 'payload'
|
||||
import { docAccessOperation, docAccessOperationGlobal } from 'payload'
|
||||
|
||||
export const getDocumentPermissions = async (args: {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
@@ -20,7 +19,7 @@ export const getDocumentPermissions = async (args: {
|
||||
id?: number | string
|
||||
req: PayloadRequest
|
||||
}): Promise<{
|
||||
docPermissions: SanitizedDocumentPermissions
|
||||
docPermissions: DocumentPermissions
|
||||
hasPublishPermission: boolean
|
||||
hasSavePermission: boolean
|
||||
}> => {
|
||||
@@ -92,13 +91,9 @@ export const getDocumentPermissions = async (args: {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do this in a better way. Only doing this bc this is how the fn was written (mutates the original object)
|
||||
const sanitizedDocPermissions = { ...docPermissions } as any as SanitizedDocumentPermissions
|
||||
sanitizePermissions(sanitizedDocPermissions)
|
||||
|
||||
const hasSavePermission = getHasSavePermission({
|
||||
collectionSlug: collectionConfig?.slug,
|
||||
docPermissions: sanitizedDocPermissions,
|
||||
docPermissions,
|
||||
globalSlug: globalConfig?.slug,
|
||||
isEditing: getIsEditing({
|
||||
id,
|
||||
@@ -108,7 +103,7 @@ export const getDocumentPermissions = async (args: {
|
||||
})
|
||||
|
||||
return {
|
||||
docPermissions: sanitizedDocPermissions,
|
||||
docPermissions,
|
||||
hasPublishPermission,
|
||||
hasSavePermission,
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type {
|
||||
DocumentPermissions,
|
||||
Payload,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedDocumentPermissions,
|
||||
SanitizedGlobalConfig,
|
||||
TypedUser,
|
||||
} from 'payload'
|
||||
|
||||
type Args = {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
docPermissions: SanitizedDocumentPermissions
|
||||
docPermissions: DocumentPermissions
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
id?: number | string
|
||||
locale?: string
|
||||
@@ -43,7 +43,7 @@ export const getVersions = async ({
|
||||
const entityConfig = collectionConfig || globalConfig
|
||||
const versionsConfig = entityConfig?.versions
|
||||
|
||||
const shouldFetchVersions = Boolean(versionsConfig && docPermissions?.readVersions)
|
||||
const shouldFetchVersions = Boolean(versionsConfig && docPermissions?.readVersions?.permission)
|
||||
|
||||
if (!shouldFetchVersions) {
|
||||
const hasPublishedDoc = Boolean((collectionConfig && id) || globalConfig)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type {
|
||||
AdminViewProps,
|
||||
CollectionPermission,
|
||||
GlobalPermission,
|
||||
PayloadComponent,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedCollectionPermission,
|
||||
SanitizedConfig,
|
||||
SanitizedGlobalConfig,
|
||||
SanitizedGlobalPermission,
|
||||
ServerSideEditViewProps,
|
||||
} from 'payload'
|
||||
import type React from 'react'
|
||||
@@ -38,7 +38,7 @@ export const getViewsFromConfig = ({
|
||||
routeSegments: string[]
|
||||
} & (
|
||||
| {
|
||||
docPermissions: SanitizedCollectionPermission | SanitizedGlobalPermission
|
||||
docPermissions: CollectionPermission | GlobalPermission
|
||||
overrideDocPermissions?: false | undefined
|
||||
}
|
||||
| {
|
||||
@@ -78,7 +78,7 @@ export const getViewsFromConfig = ({
|
||||
const [collectionEntity, collectionSlug, segment3, segment4, segment5, ...remainingSegments] =
|
||||
routeSegments
|
||||
|
||||
if (!overrideDocPermissions && !docPermissions?.read) {
|
||||
if (!overrideDocPermissions && !docPermissions?.read?.permission) {
|
||||
throw new Error('not-found')
|
||||
} else {
|
||||
// `../:id`, or `../create`
|
||||
@@ -86,7 +86,11 @@ export const getViewsFromConfig = ({
|
||||
case 3: {
|
||||
switch (segment3) {
|
||||
case 'create': {
|
||||
if (!overrideDocPermissions && 'create' in docPermissions && docPermissions.create) {
|
||||
if (
|
||||
!overrideDocPermissions &&
|
||||
'create' in docPermissions &&
|
||||
docPermissions?.create?.permission
|
||||
) {
|
||||
CustomView = {
|
||||
ComponentConfig: getCustomViewByKey(views, 'default'),
|
||||
}
|
||||
@@ -172,7 +176,7 @@ export const getViewsFromConfig = ({
|
||||
}
|
||||
|
||||
case 'versions': {
|
||||
if (!overrideDocPermissions && docPermissions?.readVersions) {
|
||||
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
ComponentConfig: getCustomViewByKey(views, 'versions'),
|
||||
}
|
||||
@@ -225,7 +229,7 @@ export const getViewsFromConfig = ({
|
||||
// `../:id/versions/:version`, etc
|
||||
default: {
|
||||
if (segment4 === 'versions') {
|
||||
if (!overrideDocPermissions && docPermissions?.readVersions) {
|
||||
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
ComponentConfig: getCustomViewByKey(views, 'version'),
|
||||
}
|
||||
@@ -277,7 +281,7 @@ export const getViewsFromConfig = ({
|
||||
if (globalConfig) {
|
||||
const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments
|
||||
|
||||
if (!overrideDocPermissions && !docPermissions?.read) {
|
||||
if (!overrideDocPermissions && !docPermissions?.read?.permission) {
|
||||
throw new Error('not-found')
|
||||
} else {
|
||||
switch (routeSegments.length) {
|
||||
@@ -319,7 +323,7 @@ export const getViewsFromConfig = ({
|
||||
}
|
||||
|
||||
case 'versions': {
|
||||
if (!overrideDocPermissions && docPermissions?.readVersions) {
|
||||
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
ComponentConfig: getCustomViewByKey(views, 'versions'),
|
||||
}
|
||||
@@ -336,7 +340,7 @@ export const getViewsFromConfig = ({
|
||||
}
|
||||
|
||||
default: {
|
||||
if (!overrideDocPermissions && docPermissions?.read) {
|
||||
if (!overrideDocPermissions && docPermissions?.read?.permission) {
|
||||
const baseRoute = [adminRoute, globalEntity, globalSlug, segment3]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
@@ -377,7 +381,7 @@ export const getViewsFromConfig = ({
|
||||
default: {
|
||||
// `../:slug/versions/:version`, etc
|
||||
if (segment3 === 'versions') {
|
||||
if (!overrideDocPermissions && docPermissions?.readVersions) {
|
||||
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
ComponentConfig: getCustomViewByKey(views, 'version'),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type {
|
||||
DefaultServerFunctionArgs,
|
||||
DocumentPermissions,
|
||||
DocumentSlots,
|
||||
PayloadRequest,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedDocumentPermissions,
|
||||
SanitizedGlobalConfig,
|
||||
StaticDescription,
|
||||
} from 'payload'
|
||||
@@ -18,7 +18,7 @@ export const renderDocumentSlots: (args: {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
hasSavePermission: boolean
|
||||
permissions: SanitizedDocumentPermissions
|
||||
permissions: DocumentPermissions
|
||||
req: PayloadRequest
|
||||
}) => DocumentSlots = (args) => {
|
||||
const { collectionConfig, globalConfig, hasSavePermission, req } = args
|
||||
|
||||
@@ -66,7 +66,7 @@ export const renderListView = async (
|
||||
visibleEntities,
|
||||
} = initPageResult
|
||||
|
||||
if (!permissions?.collections?.[collectionSlug]?.read) {
|
||||
if (!permissions?.collections?.[collectionSlug]?.read?.permission) {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ export const renderListView = async (
|
||||
|
||||
const sharedClientProps: ListComponentClientProps = {
|
||||
collectionSlug,
|
||||
hasCreatePermission: permissions?.collections?.[collectionSlug]?.create,
|
||||
hasCreatePermission: permissions?.collections?.[collectionSlug]?.create?.permission,
|
||||
newDocumentURL: formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${collectionSlug}/create`,
|
||||
|
||||
@@ -65,7 +65,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
|
||||
const comparison = compareValue?.value && currentComparisonDoc?.version // the `version` key is only present on `versions` documents
|
||||
|
||||
const canUpdate = docPermissions?.update
|
||||
const canUpdate = docPermissions?.update?.permission
|
||||
|
||||
const localeValues = locales && locales.map((locale) => locale.value)
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import type {
|
||||
Document,
|
||||
OptionObject,
|
||||
SanitizedCollectionPermission,
|
||||
SanitizedGlobalPermission,
|
||||
} from 'payload'
|
||||
import type { CollectionPermission, Document, GlobalPermission, OptionObject } from 'payload'
|
||||
|
||||
export type CompareOption = {
|
||||
label: React.ReactNode | string
|
||||
@@ -14,7 +9,7 @@ export type CompareOption = {
|
||||
|
||||
export type DefaultVersionsViewProps = {
|
||||
readonly doc: Document
|
||||
readonly docPermissions: SanitizedCollectionPermission | SanitizedGlobalPermission
|
||||
readonly docPermissions: CollectionPermission | GlobalPermission
|
||||
readonly initialComparisonDoc: Document
|
||||
readonly latestDraftVersion?: string
|
||||
readonly latestPublishedVersion?: string
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { ClientField, SanitizedFieldPermissions } from 'payload'
|
||||
import type { ClientField, FieldPermissions } from 'payload'
|
||||
import type React from 'react'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
@@ -16,10 +16,6 @@ export type DiffComponentProps<TField extends ClientField = ClientField> = {
|
||||
readonly isRichText?: boolean
|
||||
readonly locale?: string
|
||||
readonly locales?: string[]
|
||||
readonly permissions?:
|
||||
| {
|
||||
[key: string]: SanitizedFieldPermissions
|
||||
}
|
||||
| true
|
||||
readonly permissions?: Record<string, FieldPermissions>
|
||||
readonly version: any
|
||||
}
|
||||
|
||||
@@ -50,13 +50,11 @@ const RenderFieldsToDiff: React.FC<Props> = ({
|
||||
? JSON.stringify(comparison?.[fieldName])
|
||||
: comparison?.[fieldName]
|
||||
|
||||
const hasPermission =
|
||||
fieldPermissions?.[fieldName] === true || fieldPermissions?.[fieldName]?.read
|
||||
const hasPermission = fieldPermissions?.[fieldName]?.read?.permission
|
||||
|
||||
const subFieldPermissions =
|
||||
fieldPermissions?.[fieldName] === true || fieldPermissions?.[fieldName]?.fields
|
||||
const subFieldPermissions = fieldPermissions?.[fieldName]?.fields
|
||||
|
||||
if (!hasPermission) {
|
||||
if (hasPermission === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { ClientField, SanitizedFieldPermissions } from 'payload'
|
||||
import type { ClientField, FieldPermissions } from 'payload'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
import type { DiffComponents } from './fields/types.js'
|
||||
@@ -7,11 +7,7 @@ import type { DiffComponents } from './fields/types.js'
|
||||
export type Props = {
|
||||
readonly comparison: Record<string, any>
|
||||
readonly diffComponents: DiffComponents
|
||||
readonly fieldPermissions:
|
||||
| {
|
||||
[key: string]: SanitizedFieldPermissions
|
||||
}
|
||||
| true
|
||||
readonly fieldPermissions: Record<string, FieldPermissions>
|
||||
readonly fields: ClientField[]
|
||||
readonly i18n: I18nClient
|
||||
readonly locales: string[]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type {
|
||||
CollectionPermission,
|
||||
Document,
|
||||
EditViewComponent,
|
||||
GlobalPermission,
|
||||
OptionObject,
|
||||
PayloadServerReactComponent,
|
||||
SanitizedCollectionPermission,
|
||||
SanitizedGlobalPermission,
|
||||
} from 'payload'
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
@@ -33,7 +33,7 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
|
||||
const { localization } = config
|
||||
|
||||
let docPermissions: SanitizedCollectionPermission | SanitizedGlobalPermission
|
||||
let docPermissions: CollectionPermission | GlobalPermission
|
||||
let slug: string
|
||||
|
||||
let doc: Document
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload-cloud",
|
||||
"version": "3.0.0-beta.131",
|
||||
"version": "3.0.0-beta.130",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user