Compare commits

..

18 Commits

Author SHA1 Message Date
Kendell Joseph
008437d62f chore: adds db name 2024-07-30 11:59:48 -04:00
Kendell Joseph
95112a873b chore: uses memory server first 2024-07-30 11:59:48 -04:00
Kendell Joseph
25da9b62d1 chore: changes url preference priority 2024-07-30 11:59:48 -04:00
Kendell Joseph
1e5e07489c chore: adds build schema options 2024-07-30 11:59:48 -04:00
Kendell Joseph
bcb1d4eb57 chore: adds memory server uri as option 2024-07-30 11:59:48 -04:00
Kendell Joseph
268fff2645 chore: init test 2024-07-30 11:59:48 -04:00
Kendell Joseph
349d68c719 chore: makes arg optional 2024-07-30 11:59:11 -04:00
Kendell Joseph
9fefe79998 chore: uses default schemaOptions when using mongoose 2024-07-30 11:59:11 -04:00
Kendell Joseph
9f73743806 chore: preserves sibling data 2024-07-30 11:58:29 -04:00
Kendell Joseph
cb00bc5856 chore: implements arg for sibling document keys 2024-07-30 11:58:29 -04:00
Kendell Joseph
bd22bb28aa chore: adds arg for document keys 2024-07-30 11:58:29 -04:00
Kendell Joseph
ed1e460ae7 chore: sends adapter to models 2024-07-30 11:58:29 -04:00
Kendell Joseph
78ac4fd89e chore: uses mongoose adapter 2024-07-30 11:58:29 -04:00
Kendell Joseph
c328c42b72 chore: use mongoose adapter 2024-07-30 11:58:29 -04:00
Kendell Joseph
8ede4d0098 chore: removes unused config 2024-07-30 11:58:29 -04:00
Kendell Joseph
31ad34595e chore: uses mongoose adapter 2024-07-30 11:58:29 -04:00
Kendell Joseph
33278aa8d8 chore: updates documentation 2024-07-30 11:58:29 -04:00
Kendell Joseph
aad186c8b4 chore: adds schema options 2024-07-30 11:58:28 -04:00
390 changed files with 2792 additions and 5591 deletions

View File

@@ -196,7 +196,6 @@ jobs:
- postgres-custom-schema
- postgres-uuid
- supabase
- sqlite
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -549,45 +548,3 @@ jobs:
steps:
- if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
run: exit 1
publish-canary:
name: Publish Canary
if: github.ref == 'refs/heads/beta'
runs-on: ubuntu-latest
needs:
- all-green
steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Setup Node@${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Restore build
uses: actions/cache@v4
timeout-minutes: 10
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- name: Load npm token
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Canary release script
# dry run hard-coded to true for testing and no npm token provided
run: pnpm tsx ./scripts/publish-canary.ts
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true

View File

@@ -33,6 +33,9 @@ export default buildConfig({
| Option | Description |
| -------------------- | ----------- |
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
| `schemaOptions` | Customize schema options for all Mongoose schemas created internally. |
| `collections` | Options on a collection-by-collection basis. [More](#collections-options) |
| `globals` | Options for the Globals collection created by Payload. [More](#globals-options) |
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
| `migrationDir` | Customize the directory that migrations are stored. |
@@ -49,3 +52,27 @@ You can access Mongoose models as follows:
- Collection models - `payload.db.collections[myCollectionSlug]`
- Globals model - `payload.db.globals`
- Versions model (both collections and globals) - `payload.db.versions[myEntitySlug]`
### Collections Options
You can configure the way the MongoDB adapter works on a collection-by-collection basis, including customizing Mongoose `schemaOptions` for each collection schema created.
Example:
```ts
const db = mongooseAdapter({
url: 'your-url-here',
collections: {
users: {
//
schemaOptions: {
strict: false,
}
}
}
})
```
### Global Options
Payload automatically creates a single `globals` collection that correspond with any Payload globals that you define. When you initialize the `mongooseAdapter`, you can specify settings here for your globals in a similar manner to how you can for collections above. Right now, the only property available is `schemaOptions` but more may be added in the future.

View File

@@ -205,9 +205,7 @@ export const MyField: Field = {
}
```
Default values can be defined as a static value or a function that returns a value. When a `defaultValue` is defined statically, Payload's DB adapters will apply it to the database schema or models.
Functions can be written to make use of the following argument properties:
Default values can be defined as a static string or a function that returns a string. Functions are called with the following arguments:
- `user` - the authenticated user object
- `locale` - the currently selected locale string

View File

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

View File

@@ -1,7 +1,7 @@
---
title: Building Your Own Plugin
label: Build Your Own
order: 20
order: 50
desc: Starting to build your own plugin? Find everything you need and learn best practices with the Payload plugin template.
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -159,60 +159,36 @@ export const seed = async (payload: Payload): Promise<void> => {
```
## Building a Plugin
## Overview of the src folder
Now that we have our environment setup and dev project ready to go - it&apos;s time to build the plugin!
**index.ts**
First up, the `src/index.ts` file - this is where the plugin should be imported from. It is best practice not to build the plugin directly in this file, instead we use this to export the plugin and types from their respective files.
**Plugin.ts**
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
```
import type { Config } from 'payload'
export const samplePlugin =
(pluginOptions: PluginTypes) =>
(incomingConfig: Config): Config => {
// create copy of incoming config
let config = { ...incomingConfig }
/**
* This is where you could modify the
* config based on the plugin options
*/
// do something cool with the config here
// If you wanted to add a new collection:
config.collections = [
...(config.collections || []),
newCollection,
]
// If you wanted to add a new global:
config.globals = [
...(config.globals || []),
newGlobal,
]
/**
* If you wanted to add a new field to a collection:
*
* 1. Loop over collections
* 2. Find the collection you want to add the field to
* 3. Add the field to the collection
*/
// If you wanted to add to the onInit:
config.onInit = async payload => {
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
// Add additional onInit code here
}
// Finally, return the modified config
return config
}
```
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
1. First, you need to receive the existing Payload Config along with any plugin options.
2. Then set the variable `config` to be equal to a copy of the existing config.
3. From here, you can extend the config however you like!
4. Finally, return the config and you&apos;re all set.
### Spread syntax
## Spread Syntax
[Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
@@ -230,7 +206,7 @@ config.collections = [
First, you need to spread the `config.collections` to ensure that we don&apos;t lose the existing collections. Then you can add any additional collections, just as you would in a regular Payload Config.
This same logic is applied to other array and object like properties such as admin, globals and hooks:
This same logic is applied to other properties like admin, globals, hooks:
```
config.globals = [
@@ -244,10 +220,7 @@ config.hooks = {
}
```
### Extending functions
Function properties cannot use spread syntax. The way to extend them is to execute the existing function if it exists and then run your additional functionality.
Here is an example extending the `onInit` property:
Some properties will be slightly different to extend, for instance the `onInit` property:
```
config.onInit = async payload => {
@@ -258,6 +231,10 @@ config.onInit = async payload => {
}
```
If you wish to add to the `onInit`, you must include the async/await. We don&apos;t use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
In the template, we have stubbed out a basic `onInitExtension` file that you can use, if not needed feel free to delete it.
## Types
If your plugin has options, you should define and provide types for these options in a separate file which gets exported from the main `index.ts`.

View File

@@ -1,7 +1,7 @@
---
title: Form Builder Plugin
label: Form Builder
order: 40
order: 20
desc: Easily build and manage forms from the Admin Panel. Send dynamic, personalized emails and even accept and process payments.
keywords: plugins, plugin, form, forms, form builder
---

View File

@@ -1,7 +1,7 @@
---
title: Nested Docs Plugin
label: Nested Docs
order: 40
order: 20
desc: Nested documents in a parent, child, and sibling relationship.
keywords: plugins, nested, documents, parent, child, sibling, relationship
---

View File

@@ -1,7 +1,7 @@
---
title: Redirects Plugin
label: Redirects
order: 40
order: 20
desc: Automatically create redirects for your Payload application
keywords: plugins, redirects, redirect, plugin, payload, cms, seo, indexing, search, search engine
---

View File

@@ -1,7 +1,7 @@
---
title: Search Plugin
label: Search
order: 40
order: 20
desc: Generates records of your documents that are extremely fast to search on.
keywords: plugins, search, search plugin, search engine, search index, search results, search bar, search box, search field, search form, search input
---

View File

@@ -1,7 +1,7 @@
---
title: Sentry Plugin
label: Sentry
order: 40
order: 20
desc: Integrate Sentry error tracking into your Payload application
keywords: plugins, sentry, error, tracking, monitoring, logging, bug, reporting, performance
---

View File

@@ -1,7 +1,7 @@
---
title: SEO Plugin
label: SEO
order: 30
order: 20
desc: Manage SEO metadata from your Payload admin
keywords: plugins, seo, meta, search, engine, ranking, google
---

View File

@@ -1,7 +1,7 @@
---
title: Stripe Plugin
label: Stripe
order: 40
order: 20
desc: Easily accept payments with Stripe
keywords: plugins, stripe, payments, ecommerce
---

View File

@@ -94,7 +94,6 @@ _An asterisk denotes that an option is required._
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). Crop is enabled by default. [More](#crop-and-focal-point-selector) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. |
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"private": true,
"type": "module",
"scripts": {
@@ -119,7 +119,7 @@
"create-payload-app": "workspace:*",
"cross-env": "7.0.3",
"dotenv": "16.4.5",
"drizzle-orm": "0.32.1",
"drizzle-orm": "0.29.4",
"escape-html": "^1.0.3",
"execa": "5.1.1",
"form-data": "3.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -29,22 +29,9 @@ const postgresReplacement: DbAdapterReplacement = {
packageName: '@payloadcms/db-postgres',
}
const sqliteReplacement: DbAdapterReplacement = {
configReplacement: (envName = 'DATABASE_URI') => [
' db: sqliteAdapter({',
' client: {',
` url: process.env.${envName} || '',`,
' },',
' }),',
],
importReplacement: "import { sqliteAdapter } from '@payloadcms/db-sqlite'",
packageName: '@payloadcms/db-sqlite',
}
export const dbReplacements: Record<DbType, DbAdapterReplacement> = {
mongodb: mongodbReplacement,
postgres: postgresReplacement,
sqlite: sqliteReplacement,
}
type StorageAdapterReplacement = {

View File

@@ -5,7 +5,6 @@ import type { CliArgs, DbDetails, DbType } from '../types.js'
type DbChoice = {
dbConnectionPrefix: `${string}/`
dbConnectionSuffix?: string
title: string
value: DbType
}
@@ -21,12 +20,6 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
title: 'PostgreSQL (beta)',
value: 'postgres',
},
sqlite: {
dbConnectionPrefix: 'file:./',
dbConnectionSuffix: '.db',
title: 'SQLite (beta)',
value: 'sqlite',
},
}
export async function selectDb(args: CliArgs, projectName: string): Promise<DbDetails> {
@@ -44,10 +37,10 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
dbType = await p.select<{ label: string; value: DbType }[], DbType>({
initialValue: 'mongodb',
message: `Select a database`,
options: Object.values(dbChoiceRecord).map((dbChoice) => ({
label: dbChoice.title,
value: dbChoice.value,
})),
options: [
{ label: 'MongoDB', value: 'mongodb' },
{ label: 'Postgres', value: 'postgres' },
],
})
if (p.isCancel(dbType)) process.exit(0)
}
@@ -57,7 +50,7 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
let dbUri: string | symbol | undefined = undefined
const initialDbUri = `${dbChoice.dbConnectionPrefix}${
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
}${dbChoice.dbConnectionSuffix || ''}`
}`
if (args['--db-accept-recommended']) {
dbUri = initialDbUri

View File

@@ -74,11 +74,9 @@ export async function updatePayloadInProject(
info('Payload packages updated successfully.')
info(`Updating Payload Next.js files...`)
const templateFilesPath =
process.env.JEST_WORKER_ID !== undefined
? path.resolve(dirname, '../../../../templates/blank-3.0')
: path.resolve(dirname, '../..', 'dist/template')
const templateFilesPath = dirname.endsWith('dist')
? path.resolve(dirname, '../..', 'dist/template')
: path.resolve(dirname, '../../../../templates/blank-3.0')
const templateSrcDir = path.resolve(templateFilesPath, 'src/app/(payload)')

View File

@@ -57,7 +57,7 @@ interface Template {
export type PackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'
export type DbType = 'mongodb' | 'postgres' | 'sqlite'
export type DbType = 'mongodb' | 'postgres'
export type DbDetails = {
dbUri: string

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -64,7 +64,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
forceCountFn: hasNearConstraint,
lean: true,
leanWithId: true,
limit,
offset: skip,
options,
page,
pagination,

View File

@@ -1,6 +1,6 @@
import type { CollationOptions, TransactionOptions } from 'mongodb'
import type { MongoMemoryReplSet } from 'mongodb-memory-server'
import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
import type { ClientSession, ConnectOptions, Connection, SchemaOptions } from 'mongoose'
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload } from 'payload'
import fs from 'fs'
@@ -66,6 +66,15 @@ export interface Args {
* Defaults to disabled.
*/
collation?: Omit<CollationOptions, 'locale'>
/** Define Mongoose options on a collection-by-collection basis.
*/
collections?: {
[slug: string]: {
/** Define Mongoose schema options for a given collection.
*/
schemaOptions?: SchemaOptions
}
}
/** Extra configuration options */
connectOptions?: {
/** Set false to disable $facet aggregation in non-supporting databases, Defaults to true */
@@ -73,23 +82,40 @@ export interface Args {
} & ConnectOptions
/** Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false */
disableIndexHints?: boolean
/** Define Mongoose options for the globals collection.
*/
globals?: {
schemaOptions?: SchemaOptions
}
migrationDir?: string
/**
* typed as any to avoid dependency
*/
mongoMemoryServer?: MongoMemoryReplSet
/** Define default Mongoose schema options for all schemas created.
*/
schemaOptions?: SchemaOptions
transactionOptions?: TransactionOptions | false
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
url: false | string
}
export type MongooseAdapter = {
collectionOptions: {
[slug: string]: {
schemaOptions?: SchemaOptions
}
}
collections: {
[slug: string]: CollectionModel
}
connection: Connection
globals: GlobalModel
globalsOptions: {
schemaOptions?: SchemaOptions
}
mongoMemoryServer: MongoMemoryReplSet
schemaOptions?: SchemaOptions
sessions: Record<number | string, ClientSession>
versions: {
[slug: string]: CollectionModel
@@ -100,13 +126,22 @@ export type MongooseAdapter = {
declare module 'payload' {
export interface DatabaseAdapter
extends Omit<BaseDatabaseAdapter, 'sessions'>,
Omit<Args, 'migrationDir'> {
Omit<Args, 'collections' | 'globals' | 'migrationDir'> {
collectionOptions: {
[slug: string]: {
schemaOptions?: SchemaOptions
}
}
collections: {
[slug: string]: CollectionModel
}
connection: Connection
globals: GlobalModel
globalsOptions: {
schemaOptions?: SchemaOptions
}
mongoMemoryServer: MongoMemoryReplSet
schemaOptions?: SchemaOptions
sessions: Record<number | string, ClientSession>
transactionOptions: TransactionOptions
versions: {
@@ -117,10 +152,13 @@ declare module 'payload' {
export function mongooseAdapter({
autoPluralization = true,
collections,
connectOptions,
disableIndexHints = false,
globals,
migrationDir: migrationDirArg,
mongoMemoryServer,
schemaOptions,
transactionOptions = {},
url,
}: Args): DatabaseAdapterObj {
@@ -133,17 +171,21 @@ export function mongooseAdapter({
// Mongoose-specific
autoPluralization,
collectionOptions: collections || {},
collections: {},
connectOptions: connectOptions || {},
connection: undefined,
count,
disableIndexHints,
globals: undefined,
globalsOptions: globals || {},
mongoMemoryServer,
schemaOptions: schemaOptions || {},
sessions: {},
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
url,
versions: {},
// DatabaseAdapter
beginTransaction: transactionOptions ? beginTransaction : undefined,
commitTransaction,

View File

@@ -16,20 +16,22 @@ 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.config)
const schema = buildCollectionSchema(collection, this)
if (collection.versions) {
const versionModelName = getDBName({ config: collection, versions: true })
const versionCollectionFields = buildVersionCollectionFields(collection)
const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
const versionSchema = buildSchema(this, versionCollectionFields, {
disableUnique: true,
draftsEnabled: true,
indexSortableFields: this.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: false,
...this.schemaOptions,
...(this.collectionOptions[collection.slug]?.schemaOptions || {}),
},
})
@@ -57,7 +59,7 @@ export const init: Init = function init(this: MongooseAdapter) {
this.collections[collection.slug] = model
})
const model = buildGlobalModel(this.payload.config)
const model = buildGlobalModel(this)
this.globals = model
this.payload.config.globals.forEach((global) => {
@@ -66,13 +68,15 @@ export const init: Init = function init(this: MongooseAdapter) {
const versionGlobalFields = buildVersionGlobalFields(global)
const versionSchema = buildSchema(this.payload.config, versionGlobalFields, {
const versionSchema = buildSchema(this, versionGlobalFields, {
disableUnique: true,
draftsEnabled: true,
indexSortableFields: this.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: false,
...this.schemaOptions,
...(this.globalsOptions.schemaOptions || {}),
},
})

View File

@@ -1,27 +1,29 @@
import type { PaginateOptions, Schema } from 'mongoose'
import type { SanitizedCollectionConfig, SanitizedConfig } from 'payload'
import type { SanitizedCollectionConfig } from 'payload'
import paginate from 'mongoose-paginate-v2'
import type { MongooseAdapter } from '../index.js'
import getBuildQueryPlugin from '../queries/buildQuery.js'
import buildSchema from './buildSchema.js'
const buildCollectionSchema = (
collection: SanitizedCollectionConfig,
config: SanitizedConfig,
schemaOptions = {},
adapter: MongooseAdapter,
): Schema => {
const schema = buildSchema(config, collection.fields, {
const schema = buildSchema(adapter, collection.fields, {
draftsEnabled: Boolean(typeof collection?.versions === 'object' && collection.versions.drafts),
indexSortableFields: config.indexSortableFields,
indexSortableFields: adapter.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: collection.timestamps !== false,
...schemaOptions,
...adapter.schemaOptions,
...(adapter.collectionOptions[collection.slug]?.schemaOptions || {}),
},
})
if (config.indexSortableFields && collection.timestamps !== false) {
if (adapter.payload.config.indexSortableFields && collection.timestamps !== false) {
schema.index({ updatedAt: 1 })
schema.index({ createdAt: 1 })
}

View File

@@ -1,27 +1,34 @@
import type { SanitizedConfig } from 'payload'
import mongoose from 'mongoose'
import type { MongooseAdapter } from '../index.js'
import type { GlobalModel } from '../types.js'
import getBuildQueryPlugin from '../queries/buildQuery.js'
import buildSchema from './buildSchema.js'
export const buildGlobalModel = (config: SanitizedConfig): GlobalModel | null => {
if (config.globals && config.globals.length > 0) {
export const buildGlobalModel = (adapter: MongooseAdapter): GlobalModel | null => {
if (adapter.payload.config.globals && adapter.payload.config.globals.length > 0) {
const globalsSchema = new mongoose.Schema(
{},
{ discriminatorKey: 'globalType', minimize: false, timestamps: true },
{
discriminatorKey: 'globalType',
minimize: false,
...adapter.schemaOptions,
...(adapter.globalsOptions.schemaOptions || {}),
timestamps: true,
},
)
globalsSchema.plugin(getBuildQueryPlugin())
const Globals = mongoose.model('globals', globalsSchema, 'globals') as unknown as GlobalModel
Object.values(config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema(config, globalConfig.fields, {
Object.values(adapter.payload.config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema(adapter, globalConfig.fields, {
options: {
minimize: false,
...adapter.schemaOptions,
...(adapter.globalsOptions.schemaOptions || {}),
},
})
Globals.discriminator(globalConfig.slug, globalSchema)

View File

@@ -19,7 +19,6 @@ import type {
RelationshipField,
RichTextField,
RowField,
SanitizedConfig,
SanitizedLocalizationConfig,
SelectField,
Tab,
@@ -37,6 +36,8 @@ import {
tabHasName,
} from 'payload/shared'
import type { MongooseAdapter } from '../index.js'
export type BuildSchemaOptions = {
allowIDField?: boolean
disableUnique?: boolean
@@ -48,23 +49,13 @@ export type BuildSchemaOptions = {
type FieldSchemaGenerator = (
field: Field,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
) => void
/**
* get a field's defaultValue only if defined and not dynamic so that it can be set on the field schema
* @param field
*/
const formatDefaultValue = (field: FieldAffectingData) =>
typeof field.defaultValue !== 'undefined' && typeof field.defaultValue !== 'function'
? field.defaultValue
: undefined
const formatBaseSchema = (field: FieldAffectingData, buildSchemaOptions: BuildSchemaOptions) => {
const { disableUnique, draftsEnabled, indexSortableFields } = buildSchemaOptions
const schema: SchemaTypeOptions<unknown> = {
default: formatDefaultValue(field),
index: field.index || (!disableUnique && field.unique) || indexSortableFields || false,
required: false,
unique: (!disableUnique && field.unique) || false,
@@ -97,10 +88,10 @@ const localizeSchema = (
if (fieldIsLocalized(entity) && localization && Array.isArray(localization.locales)) {
return {
type: localization.localeCodes.reduce(
(localeSchema, locale) => ({
...localeSchema,
[locale]: schema,
}),
(localeSchema, locale) => {
localeSchema[locale] = schema
return localeSchema
},
{
_id: false,
},
@@ -112,7 +103,7 @@ const localizeSchema = (
}
const buildSchema = (
config: SanitizedConfig,
adapter: MongooseAdapter,
configFields: Field[],
buildSchemaOptions: BuildSchemaOptions = {},
): Schema => {
@@ -140,7 +131,7 @@ const buildSchema = (
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[field.type]
if (addFieldSchema) {
addFieldSchema(field, schema, config, buildSchemaOptions)
addFieldSchema(field, schema, adapter, buildSchemaOptions)
}
}
})
@@ -152,55 +143,77 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
array: (
field: ArrayField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
) => {
const baseSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: [
buildSchema(config, field.fields, {
buildSchema(adapter, field.fields, {
allowIDField: true,
disableUnique: buildSchemaOptions.disableUnique,
draftsEnabled: buildSchemaOptions.draftsEnabled,
options: {
minimize: false,
...(buildSchemaOptions.options || {}),
_id: false,
id: false,
minimize: false,
timestamps: false,
},
}),
],
default: undefined,
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
blocks: (
field: BlockField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const fieldSchema = {
type: [new mongoose.Schema({}, { _id: false, discriminatorKey: 'blockType' })],
type: [
new mongoose.Schema(
{},
{
_id: false,
discriminatorKey: 'blockType',
...(buildSchemaOptions.options || {}),
timestamps: false,
},
),
],
default: undefined,
}
schema.add({
[field.name]: localizeSchema(field, fieldSchema, config.localization),
[field.name]: localizeSchema(field, fieldSchema, adapter.payload.config.localization),
})
field.blocks.forEach((blockItem: Block) => {
const blockSchema = new mongoose.Schema({}, { _id: false, id: false })
const blockSchema = new mongoose.Schema(
{},
{
...(buildSchemaOptions.options || {}),
_id: false,
id: false,
timestamps: false,
},
)
blockItem.fields.forEach((blockField) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[blockField.type]
if (addFieldSchema) {
addFieldSchema(blockField, blockSchema, config, buildSchemaOptions)
addFieldSchema(blockField, blockSchema, adapter, buildSchemaOptions)
}
})
if (field.localized && config.localization) {
config.localization.localeCodes.forEach((localeCode) => {
if (field.localized && adapter.payload.config.localization) {
adapter.payload.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)
})
@@ -213,69 +226,69 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
checkbox: (
field: CheckboxField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Boolean }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
code: (
field: CodeField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
collapsible: (
field: CollapsibleField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
field.fields.forEach((subField: Field) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {
addFieldSchema(subField, schema, config, buildSchemaOptions)
addFieldSchema(subField, schema, adapter, buildSchemaOptions)
}
})
},
date: (
field: DateField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Date }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
email: (
field: EmailField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
group: (
field: GroupField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const formattedBaseSchema = formatBaseSchema(field, buildSchemaOptions)
@@ -288,26 +301,28 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
const baseSchema = {
...formattedBaseSchema,
type: buildSchema(config, field.fields, {
type: buildSchema(adapter, field.fields, {
disableUnique: buildSchemaOptions.disableUnique,
draftsEnabled: buildSchemaOptions.draftsEnabled,
indexSortableFields,
options: {
minimize: false,
...(buildSchemaOptions.options || {}),
_id: false,
id: false,
minimize: false,
timestamps: false,
},
}),
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
json: (
field: JSONField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -316,13 +331,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
number: (
field: NumberField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -331,13 +346,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
point: (
field: PointField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema: SchemaTypeOptions<unknown> = {
@@ -347,7 +362,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
},
coordinates: {
type: [Number],
default: formatDefaultValue(field),
default: field.defaultValue || undefined,
required: false,
},
}
@@ -356,7 +371,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
if (field.index === true || field.index === undefined) {
@@ -365,8 +380,8 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
indexOptions.sparse = true
indexOptions.unique = true
}
if (field.localized && config.localization) {
config.localization.locales.forEach((locale) => {
if (field.localized && adapter.payload.config.localization) {
adapter.payload.config.localization.locales.forEach((locale) => {
schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions)
})
} else {
@@ -377,7 +392,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
radio: (
field: RadioField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -390,21 +405,21 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
relationship: (
field: RelationshipField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
) => {
const hasManyRelations = Array.isArray(field.relationTo)
let schemaToReturn: { [key: string]: any } = {}
if (field.localized && config.localization) {
if (field.localized && adapter.payload.config.localization) {
schemaToReturn = {
type: config.localization.localeCodes.reduce((locales, locale) => {
type: adapter.payload.config.localization.localeCodes.reduce((locales, locale) => {
let localeSchema: { [key: string]: any } = {}
if (hasManyRelations) {
@@ -428,9 +443,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
return {
...locales,
[locale]: field.hasMany
? { type: [localeSchema], default: formatDefaultValue(field) }
: localeSchema,
[locale]: field.hasMany ? { type: [localeSchema], default: undefined } : localeSchema,
}
}, {}),
localized: true,
@@ -450,7 +463,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
if (field.hasMany) {
schemaToReturn = {
type: [schemaToReturn],
default: formatDefaultValue(field),
default: undefined,
}
}
} else {
@@ -463,7 +476,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
if (field.hasMany) {
schemaToReturn = {
type: [schemaToReturn],
default: formatDefaultValue(field),
default: undefined,
}
}
}
@@ -475,7 +488,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
richText: (
field: RichTextField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -484,27 +497,27 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
row: (
field: RowField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
field.fields.forEach((subField: Field) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {
addFieldSchema(subField, schema, config, buildSchemaOptions)
addFieldSchema(subField, schema, adapter, buildSchemaOptions)
}
})
},
select: (
field: SelectField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -524,39 +537,41 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
[field.name]: localizeSchema(
field,
field.hasMany ? [baseSchema] : baseSchema,
config.localization,
adapter.payload.config.localization,
),
})
},
tabs: (
field: TabsField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
field.tabs.forEach((tab) => {
if (tabHasName(tab)) {
const baseSchema = {
type: buildSchema(config, tab.fields, {
type: buildSchema(adapter, tab.fields, {
disableUnique: buildSchemaOptions.disableUnique,
draftsEnabled: buildSchemaOptions.draftsEnabled,
options: {
minimize: false,
...(buildSchemaOptions.options || {}),
_id: false,
id: false,
minimize: false,
timestamps: false,
},
}),
}
schema.add({
[tab.name]: localizeSchema(tab, baseSchema, config.localization),
[tab.name]: localizeSchema(tab, baseSchema, adapter.payload.config.localization),
})
} else {
tab.fields.forEach((subField: Field) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {
addFieldSchema(subField, schema, config, buildSchemaOptions)
addFieldSchema(subField, schema, adapter, buildSchemaOptions)
}
})
}
@@ -565,7 +580,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
text: (
field: TextField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -574,25 +589,25 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
textarea: (
field: TextareaField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
upload: (
field: UploadField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -602,7 +617,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -47,8 +47,8 @@
"dependencies": {
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.23.2",
"drizzle-orm": "0.32.1",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-orm": "0.29.4",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",

View File

@@ -53,7 +53,6 @@ export const connect: Connect = async function connect(
const { hotReload } = options
this.schema = {
pgSchema: this.pgSchema,
...this.tables,
...this.relations,
...this.enums,

View File

@@ -1,4 +1,4 @@
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { CreateMigration } from 'payload'
import fs from 'fs'
@@ -25,7 +25,7 @@ export const createMigration: CreateMigration = async function createMigration(
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/api')
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
const drizzleJsonAfter = generateDrizzleJson(this.schema)
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
@@ -49,12 +49,6 @@ export const createMigration: CreateMigration = async function createMigration(
let drizzleJsonBefore = defaultDrizzleSnapshot
if (this.schemaName) {
drizzleJsonBefore.schemas = {
[this.schemaName]: this.schemaName,
}
}
if (!upSQL) {
// Get latest migration snapshot
const latestSnapshot = fs

View File

@@ -1,4 +1,4 @@
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
export const defaultDrizzleSnapshot: DrizzleSnapshotJSON = {
id: '00000000-0000-0000-0000-000000000000',
@@ -7,11 +7,10 @@ export const defaultDrizzleSnapshot: DrizzleSnapshotJSON = {
schemas: {},
tables: {},
},
dialect: 'postgresql',
dialect: 'pg',
enums: {},
prevId: '00000000-0000-0000-0000-00000000000',
schemas: {},
sequences: {},
tables: {},
version: '7',
version: '5',
}

View File

@@ -32,7 +32,6 @@ import {
updateOne,
updateVersion,
} from '@payloadcms/drizzle'
import { type PgSchema, pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { createDatabaseAdapter } from 'payload'
import type { Args, PostgresAdapter } from './types.js'
@@ -63,19 +62,12 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
const migrationDir = findMigrationDir(args.migrationDir)
let resolveInitializing
let rejectInitializing
let adapterSchema: PostgresAdapter['pgSchema']
const initializing = new Promise<void>((res, rej) => {
resolveInitializing = res
rejectInitializing = rej
})
if (args.schemaName) {
adapterSchema = pgSchema(args.schemaName)
} else {
adapterSchema = { enum: pgEnum, table: pgTable }
}
return createDatabaseAdapter<PostgresAdapter>({
name: 'postgres',
defaultDrizzleSnapshot,
@@ -91,7 +83,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger,
operators: operatorMap,
pgSchema: adapterSchema,
pgSchema: undefined,
pool: undefined,
poolOptions: args.pool,
push: args.push,

View File

@@ -1,6 +1,7 @@
import type { Init, SanitizedCollectionConfig } from 'payload'
import { createTableName } from '@payloadcms/drizzle'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
import toSnakeCase from 'to-snake-case'
@@ -9,8 +10,13 @@ import type { PostgresAdapter } from './types.js'
import { buildTable } from './schema/build.js'
export const init: Init = function init(this: PostgresAdapter) {
if (this.schemaName) {
this.pgSchema = pgSchema(this.schemaName)
} else {
this.pgSchema = { table: pgTable }
}
if (this.payload.config.localization) {
this.enums.enum__locales = this.pgSchema.enum(
this.enums.enum__locales = pgEnum(
'_locales',
this.payload.config.localization.locales.map(({ code }) => code) as [string, ...string[]],
)

View File

@@ -1,5 +1,5 @@
import type { TransactionPg } from '@payloadcms/drizzle/types'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { Payload, PayloadRequest } from 'payload'
import { sql } from 'drizzle-orm'
@@ -43,7 +43,7 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => {
const dir = payload.db.migrationDir
// get the drizzle migrateUpSQL from drizzle using the last schema
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/api')
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
const drizzleJsonAfter = generateDrizzleJson(adapter.schema)
// Get the previous migration snapshot

View File

@@ -2,4 +2,4 @@ import type { RequireDrizzleKit } from '@payloadcms/drizzle/types'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/api')
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/payload')

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type {
ForeignKeyBuilder,

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import { index, uniqueIndex } from 'drizzle-orm/pg-core'
import type { GenericColumn } from '../types.js'

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
import type { Field, TabAsField } from 'payload'
@@ -18,6 +19,7 @@ import {
integer,
jsonb,
numeric,
pgEnum,
text,
timestamp,
varchar,
@@ -33,7 +35,6 @@ import { buildTable } from './build.js'
import { createIndex } from './createIndex.js'
import { idToUUID } from './idToUUID.js'
import { parentIDColumnMap } from './parentIDColumnMap.js'
import { withDefault } from './withDefault.js'
type Args = {
adapter: PostgresAdapter
@@ -169,14 +170,14 @@ export const traverseFields = ({
)
}
} else {
targetTable[fieldName] = withDefault(varchar(columnName), field)
targetTable[fieldName] = varchar(columnName)
}
break
}
case 'email':
case 'code':
case 'textarea': {
targetTable[fieldName] = withDefault(varchar(columnName), field)
targetTable[fieldName] = varchar(columnName)
break
}
@@ -198,26 +199,23 @@ export const traverseFields = ({
)
}
} else {
targetTable[fieldName] = withDefault(numeric(columnName), field)
targetTable[fieldName] = numeric(columnName)
}
break
}
case 'richText':
case 'json': {
targetTable[fieldName] = withDefault(jsonb(columnName), field)
targetTable[fieldName] = jsonb(columnName)
break
}
case 'date': {
targetTable[fieldName] = withDefault(
timestamp(columnName, {
mode: 'string',
precision: 3,
withTimezone: true,
}),
field,
)
targetTable[fieldName] = timestamp(columnName, {
mode: 'string',
precision: 3,
withTimezone: true,
})
break
}
@@ -236,7 +234,7 @@ export const traverseFields = ({
throwValidationError,
})
adapter.enums[enumName] = adapter.pgSchema.enum(
adapter.enums[enumName] = pgEnum(
enumName,
field.options.map((option) => {
if (optionIsObject(option)) {
@@ -313,13 +311,13 @@ export const traverseFields = ({
}),
)
} else {
targetTable[fieldName] = withDefault(adapter.enums[enumName](fieldName), field)
targetTable[fieldName] = adapter.enums[enumName](fieldName)
}
break
}
case 'checkbox': {
targetTable[fieldName] = withDefault(boolean(columnName), field)
targetTable[fieldName] = boolean(columnName)
break
}

View File

@@ -1,17 +0,0 @@
import type { PgColumnBuilder } from 'drizzle-orm/pg-core'
import type { FieldAffectingData } from 'payload'
export const withDefault = (
column: PgColumnBuilder,
field: FieldAffectingData,
): PgColumnBuilder => {
if (typeof field.defaultValue === 'undefined' || typeof field.defaultValue === 'function')
return column
if (typeof field.defaultValue === 'string' && field.defaultValue.includes("'")) {
const escapedString = field.defaultValue.replaceAll("'", "''")
return column.default(escapedString)
}
return column.default(field.defaultValue)
}

View File

@@ -4,7 +4,7 @@ import type {
DrizzleAdapter,
TransactionPg,
} from '@payloadcms/drizzle/types'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type {
ColumnBaseConfig,
ColumnDataType,
@@ -21,7 +21,6 @@ import type {
PgSchema,
PgTableWithColumns,
PgTransactionConfig,
pgEnum,
} from 'drizzle-orm/pg-core'
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
import type { Payload, PayloadRequest } from 'payload'
@@ -107,13 +106,6 @@ type PostgresDrizzleAdapter = Omit<
| 'relations'
>
type Schema =
| {
enum: typeof pgEnum
table: PgTableFn
}
| PgSchema
export type PostgresAdapter = {
countDistinct: CountDistinct
defaultDrizzleSnapshot: DrizzleSnapshotJSON
@@ -133,7 +125,7 @@ export type PostgresAdapter = {
localesSuffix?: string
logger: DrizzleConfig['logger']
operators: Operators
pgSchema?: Schema
pgSchema?: { table: PgTableFn } | PgSchema
pool: Pool
poolOptions: Args['pool']
push: boolean
@@ -141,6 +133,7 @@ export type PostgresAdapter = {
relations: Record<string, GenericRelation>
relationshipsSuffix?: string
resolveInitializing: () => void
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
schemaName?: Args['schemaName']
sessions: {
[id: string]: {
@@ -182,7 +175,7 @@ declare module 'payload' {
rejectInitializing: () => void
relationshipsSuffix?: string
resolveInitializing: () => void
schema: Record<string, unknown>
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
schemaName?: Args['schemaName']
tableNameMap: Map<string, string>
versionsSuffix?: string

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -46,8 +46,8 @@
"@libsql/client": "^0.6.2",
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.23.2",
"drizzle-orm": "0.32.1",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-orm": "0.29.4",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"

View File

@@ -1,4 +1,4 @@
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { CreateMigration } from 'payload'
import fs from 'fs'
@@ -25,7 +25,7 @@ export const createMigration: CreateMigration = async function createMigration(
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = require('drizzle-kit/api')
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = require('drizzle-kit/payload')
const drizzleJsonAfter = await generateSQLiteDrizzleJson(this.schema)
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')

View File

@@ -1,4 +1,4 @@
import type { DrizzleSQLiteSnapshotJSON } from 'drizzle-kit/api'
import type { DrizzleSQLiteSnapshotJSON } from 'drizzle-kit/payload'
export const defaultDrizzleSnapshot: DrizzleSQLiteSnapshotJSON = {
id: '00000000-0000-0000-0000-000000000000',
@@ -10,5 +10,5 @@ export const defaultDrizzleSnapshot: DrizzleSQLiteSnapshotJSON = {
enums: {},
prevId: '00000000-0000-0000-0000-00000000000',
tables: {},
version: '6',
version: '5',
}

View File

@@ -105,7 +105,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
versionsSuffix: args.versionsSuffix || '_v',
// DatabaseAdapter
beginTransaction: args.transactionOptions ? beginTransaction : undefined,
beginTransaction: args.transactionOptions === false ? undefined : beginTransaction,
commitTransaction,
connect,
convertPathToJSONTraversal,

View File

@@ -10,6 +10,6 @@ export const requireDrizzleKit: RequireDrizzleKit = () => {
const {
generateSQLiteDrizzleJson: generateDrizzleJson,
pushSQLiteSchema: pushSchema,
} = require('drizzle-kit/api')
} = require('drizzle-kit/payload')
return { generateDrizzleJson, pushSchema }
}

View File

@@ -1,8 +1,9 @@
import type { Relation } from 'drizzle-orm'
/* eslint-disable no-param-reassign */
import type { ColumnDataType, Relation } from 'drizzle-orm'
import type {
AnySQLiteColumn,
ForeignKeyBuilder,
IndexBuilder,
SQLiteColumn,
SQLiteColumnBuilder,
SQLiteTableWithColumns,
UniqueConstraintBuilder,
@@ -31,7 +32,18 @@ import { traverseFields } from './traverseFields.js'
export type BaseExtraConfig = Record<
string,
(cols: {
[x: string]: AnySQLiteColumn
[x: string]: SQLiteColumn<{
baseColumn: never
columnType: string
data: unknown
dataType: ColumnDataType
driverParam: unknown
enumValues: string[]
hasDefault: false
name: string
notNull: false
tableName: string
}>
}) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
>

View File

@@ -1,7 +1,8 @@
import type { AnySQLiteColumn} from 'drizzle-orm/sqlite-core';
/* eslint-disable no-param-reassign */
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
import type { GenericColumn } from '../types.js'
type CreateIndexArgs = {
columnName: string
name: string | string[]
@@ -10,7 +11,7 @@ type CreateIndexArgs = {
}
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
return (table: { [x: string]: AnySQLiteColumn }) => {
return (table: { [x: string]: GenericColumn }) => {
let columns
if (Array.isArray(name)) {
columns = name

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
import type { Field, TabAsField } from 'payload'
@@ -29,7 +30,6 @@ import { buildTable } from './build.js'
import { createIndex } from './createIndex.js'
import { getIDColumn } from './getIDColumn.js'
import { idToUUID } from './idToUUID.js'
import { withDefault } from './withDefault.js'
type Args = {
adapter: SQLiteAdapter
@@ -166,14 +166,14 @@ export const traverseFields = ({
)
}
} else {
targetTable[fieldName] = withDefault(text(columnName), field)
targetTable[fieldName] = text(columnName)
}
break
}
case 'email':
case 'code':
case 'textarea': {
targetTable[fieldName] = withDefault(text(columnName), field)
targetTable[fieldName] = text(columnName)
break
}
@@ -195,19 +195,19 @@ export const traverseFields = ({
)
}
} else {
targetTable[fieldName] = withDefault(numeric(columnName), field)
targetTable[fieldName] = numeric(columnName)
}
break
}
case 'richText':
case 'json': {
targetTable[fieldName] = withDefault(text(columnName, { mode: 'json' }), field)
targetTable[fieldName] = text(columnName, { mode: 'json' })
break
}
case 'date': {
targetTable[fieldName] = withDefault(text(columnName), field)
targetTable[fieldName] = text(columnName)
break
}
@@ -295,13 +295,13 @@ export const traverseFields = ({
}),
)
} else {
targetTable[fieldName] = withDefault(text(fieldName, { enum: options }), field)
targetTable[fieldName] = text(fieldName, { enum: options })
}
break
}
case 'checkbox': {
targetTable[fieldName] = withDefault(integer(columnName, { mode: 'boolean' }), field)
targetTable[fieldName] = integer(columnName, { mode: 'boolean' })
break
}

View File

@@ -1,17 +0,0 @@
import type { SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
import type { FieldAffectingData } from 'payload'
export const withDefault = (
column: SQLiteColumnBuilder,
field: FieldAffectingData,
): SQLiteColumnBuilder => {
if (typeof field.defaultValue === 'undefined' || typeof field.defaultValue === 'function')
return column
if (typeof field.defaultValue === 'string' && field.defaultValue.includes("'")) {
const escapedString = field.defaultValue.replaceAll("'", "''")
return column.default(escapedString)
}
return column.default(field.defaultValue)
}

View File

@@ -1,10 +1,10 @@
import type { Client, Config, ResultSet } from '@libsql/client'
import type { Operators } from '@payloadcms/drizzle'
import type { BuildQueryJoinAliases, DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { ColumnDataType, DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
import type {
AnySQLiteColumn,
SQLiteColumn,
SQLiteInsertOnConflictDoUpdateConfig,
SQLiteTableWithColumns,
SQLiteTransactionConfig,
@@ -25,8 +25,24 @@ export type Args = {
versionsSuffix?: string
}
export type GenericColumn = SQLiteColumn<
{
baseColumn: never
columnType: string
data: unknown
dataType: ColumnDataType
driverParam: unknown
enumValues: string[]
hasDefault: false
name: string
notNull: false
tableName: string
},
object
>
export type GenericColumns = {
[x: string]: AnySQLiteColumn
[x: string]: GenericColumn
}
export type GenericTable = SQLiteTableWithColumns<{

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {
@@ -39,7 +39,7 @@
},
"dependencies": {
"console-table-printer": "2.11.2",
"drizzle-orm": "0.32.1",
"drizzle-orm": "0.29.4",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"

View File

@@ -1,4 +1,5 @@
import type { Count , SanitizedCollectionConfig } from 'payload'
import type { Count } from 'payload'
import type { SanitizedCollectionConfig } from 'payload'
import toSnakeCase from 'to-snake-case'
@@ -14,7 +15,7 @@ export const count: Count = async function count(
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const { joins, where } = await buildQuery({
adapter: this,

View File

@@ -10,7 +10,7 @@ export const create: Create = async function create(
this: DrizzleAdapter,
{ collection: collectionSlug, data, req },
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))

View File

@@ -10,7 +10,7 @@ export async function createGlobal<T extends Record<string, unknown>>(
this: DrizzleAdapter,
{ slug, data, req = {} as PayloadRequest }: CreateGlobalArgs,
): Promise<T> {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))

View File

@@ -12,7 +12,7 @@ export async function createGlobalVersion<T extends TypeWithID>(
this: DrizzleAdapter,
{ autosave, globalSlug, req = {} as PayloadRequest, versionData }: CreateGlobalVersionArgs,
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
const tableName = this.tableNameMap.get(`_${toSnakeCase(global.slug)}${this.versionsSuffix}`)

View File

@@ -18,7 +18,7 @@ export async function createVersion<T extends TypeWithID>(
versionData,
}: CreateVersionArgs<T>,
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const defaultTableName = toSnakeCase(collection.slug)

View File

@@ -11,7 +11,7 @@ export const deleteMany: DeleteMany = async function deleteMany(
this: DrizzleAdapter,
{ collection, req = {} as PayloadRequest, where },
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const collectionConfig = this.payload.collections[collection].config
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))

View File

@@ -14,7 +14,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
this: DrizzleAdapter,
{ collection: collectionSlug, req = {} as PayloadRequest, where: whereArg },
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))

View File

@@ -12,7 +12,7 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
this: DrizzleAdapter,
{ collection, locale, req = {} as PayloadRequest, where: where },
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = this.tableNameMap.get(

View File

@@ -120,7 +120,6 @@ export type RequireDrizzleKit = () => {
pushSchema: (
schema: Record<string, unknown>,
drizzle: DrizzleAdapter['drizzle'],
filterSchema?: string[],
) => Promise<{ apply; hasDataLoss; warnings }>
}

View File

@@ -12,7 +12,7 @@ export const updateOne: UpdateOne = async function updateOne(
this: DrizzleAdapter,
{ id, collection: collectionSlug, data, draft, locale, req, where: whereArg },
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
const whereToUse = whereArg || { id: { equals: id } }

View File

@@ -10,7 +10,7 @@ export async function updateGlobal<T extends Record<string, unknown>>(
this: DrizzleAdapter,
{ slug, data, req = {} as PayloadRequest }: UpdateGlobalArgs,
): Promise<T> {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))

View File

@@ -25,7 +25,7 @@ export async function updateGlobalVersion<T extends TypeWithID>(
where: whereArg,
}: UpdateGlobalVersionArgs<T>,
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
({ slug }) => slug === global,
)

View File

@@ -25,7 +25,7 @@ export async function updateVersion<T extends TypeWithID>(
where: whereArg,
}: UpdateVersionArgs<T>,
) {
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const db = this.sessions[await req.transactionID]?.db || this.drizzle
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const whereToUse = whereArg || { id: { equals: id } }
const tableName = this.tableNameMap.get(

View File

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

View File

@@ -12,11 +12,7 @@ export const pushDevSchema = async (adapter: DrizzleAdapter) => {
const { pushSchema } = adapter.requireDrizzleKit()
// This will prompt if clarifications are needed for Drizzle to push new schema
const { apply, hasDataLoss, warnings } = await pushSchema(
adapter.schema,
adapter.drizzle,
adapter.schemaName ? [adapter.schemaName] : undefined,
)
const { apply, hasDataLoss, warnings } = await pushSchema(adapter.schema, adapter.drizzle)
if (warnings.length) {
let message = `Warnings detected during schema push: \n\n${warnings.join('\n')}\n\n`

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -7,7 +7,6 @@ import type { Context } from '../types.js'
export type Resolver = (
_: unknown,
args: {
draft?: boolean
id: number | string
},
context: {
@@ -21,7 +20,6 @@ export default function restoreVersionResolver(collection: Collection): Resolver
id: args.id,
collection,
depth: 0,
draft: args.draft,
req: isolateObjectProperty(context.req, 'transactionID'),
}

View File

@@ -7,7 +7,6 @@ import type { Context } from '../types.js'
type Resolver = (
_: unknown,
args: {
draft?: boolean
id: number | string
},
context: {
@@ -19,7 +18,6 @@ export default function restoreVersionResolver(globalConfig: SanitizedGlobalConf
const options = {
id: args.id,
depth: 0,
draft: args.draft,
globalConfig,
req: isolateObjectProperty(context.req, 'transactionID'),
}

View File

@@ -342,7 +342,6 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
type: collection.graphQL.type,
args: {
id: { type: versionIDType },
draft: { type: GraphQLBoolean },
},
resolve: restoreVersionResolver(collection),
}

View File

@@ -133,7 +133,6 @@ function initGlobalsGraphQL({ config, graphqlResult }: InitGlobalsGraphQLArgs):
type: graphqlResult.globals.graphQL[slug].versionType,
args: {
id: { type: idType },
draft: { type: GraphQLBoolean },
...(config.localization
? {
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
@@ -172,7 +171,6 @@ function initGlobalsGraphQL({ config, graphqlResult }: InitGlobalsGraphQLArgs):
type: graphqlResult.globals.graphQL[slug].type,
args: {
id: { type: idType },
draft: { type: GraphQLBoolean },
},
resolve: restoreVersionResolver(global),
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.71",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -35,7 +35,7 @@
position: absolute;
width: 100%;
height: 100%;
border-radius: var(--style-radius-s);
border-radius: 2px;
background-color: var(--theme-elevation-50);
opacity: 0;
}
@@ -51,7 +51,6 @@
}
&--active {
font-weight: 600;
&::before {
opacity: 1;
background-color: var(--theme-elevation-100);
@@ -79,15 +78,14 @@
gap: 4px;
width: 100%;
height: 100%;
line-height: base(1.2);
padding: base(0.2) base(0.6);
padding: calc(var(--base) / 2) calc(var(--base));
}
&__count {
line-height: base(0.8);
min-width: base(0.8);
min-width: 22px;
text-align: center;
padding: 2px 7px;
background-color: var(--theme-elevation-100);
border-radius: var(--style-radius-s);
border-radius: 1px;
}
}

View File

@@ -1,6 +1,6 @@
'use client'
import { useDocumentInfo } from '@payloadcms/ui'
import React from 'react'
import React, { Fragment } from 'react'
import { baseClass } from '../../Tab/index.js'
@@ -12,14 +12,13 @@ export const VersionsPill: React.FC = () => {
// documents that are version enabled _always_ have at least one version
const hasVersions = versions?.totalDocs > 0
if (hasVersions)
return (
<span
className={[`${baseClass}__count`, hasVersions ? `${baseClass}__count--has-count` : '']
.filter(Boolean)
.join(' ')}
>
{versions.totalDocs.toString()}
</span>
)
return (
<span
className={[`${baseClass}__count`, hasVersions ? `${baseClass}__count--has-count` : '']
.filter(Boolean)
.join(' ')}
>
{hasVersions ? versions.totalDocs.toString() : <Fragment>&nbsp;</Fragment>}
</span>
)
}

View File

@@ -1,45 +0,0 @@
'use client'
import type { LoginWithUsernameOptions } from 'payload'
import { EmailField, TextField, useTranslation } from '@payloadcms/ui'
import { email, username } from 'payload/shared'
import React from 'react'
type Props = {
loginWithUsername?: LoginWithUsernameOptions | false
}
export const EmailAndUsernameFields: React.FC<Props> = ({ loginWithUsername }) => {
const { t } = useTranslation()
const requireEmail = !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail)
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
const showEmailField =
!loginWithUsername || loginWithUsername?.requireEmail || loginWithUsername?.allowEmailLogin
const showUsernameField = Boolean(loginWithUsername)
return (
<React.Fragment>
{showEmailField && (
<EmailField
autoComplete="email"
label={t('general:email')}
name="email"
path="email"
required={requireEmail}
validate={email}
/>
)}
{showUsernameField && (
<TextField
label={t('authentication:username')}
name="username"
path="username"
required={requireUsername}
validate={username}
/>
)}
</React.Fragment>
)
}

View File

@@ -37,12 +37,10 @@ const Component: React.FC<{
<p>{t('general:changesNotSaved')}</p>
</div>
<div className={`${baseClass}__controls`}>
<Button buttonStyle="secondary" onClick={onCancel} size="large">
<Button buttonStyle="secondary" onClick={onCancel}>
{t('general:stayOnThisPage')}
</Button>
<Button onClick={onConfirm} size="large">
{t('general:leaveAnyway')}
</Button>
<Button onClick={onConfirm}>{t('general:leaveAnyway')}</Button>
</div>
</div>
</Modal>

View File

@@ -85,18 +85,9 @@ export const DefaultNavClient: React.FC = () => {
const LinkElement = Link || 'a'
const activeCollection = window?.location?.pathname
?.split('/')
.find(
(_, index, arr) =>
arr[index - 1] === 'collections' || arr[index - 1] === 'globals',
)
return (
<LinkElement
className={[`${baseClass}__link`, activeCollection === entity?.slug && `active`]
.filter(Boolean)
.join(' ')}
className={`${baseClass}__link`}
href={href}
id={id}
key={i}

View File

@@ -5,6 +5,7 @@ import { initI18n, rtlLanguages } from '@payloadcms/translations'
import { RootProvider } from '@payloadcms/ui'
import '@payloadcms/ui/scss/app.scss'
import { buildComponentMap } from '@payloadcms/ui/utilities/buildComponentMap'
import { Merriweather } from 'next/font/google'
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
import { createClientConfig, parseCookies } from 'payload'
import React from 'react'
@@ -15,6 +16,14 @@ import { getRequestTheme } from '../../utilities/getRequestTheme.js'
import { DefaultEditView } from '../../views/Edit/Default/index.js'
import { DefaultListView } from '../../views/List/Default/index.js'
const merriweather = Merriweather({
display: 'swap',
style: ['normal', 'italic'],
subsets: ['latin'],
variable: '--font-serif',
weight: ['400', '900'],
})
export const metadata = {
description: 'Generated by Next.js',
title: 'Next.js',
@@ -91,7 +100,7 @@ export const RootLayout = async ({
})
return (
<html data-theme={theme} dir={dir} lang={languageCode}>
<html className={merriweather.variable} data-theme={theme} dir={dir} lang={languageCode}>
<body>
<RootProvider
componentMap={componentMap}

View File

@@ -14,7 +14,6 @@ export const restoreVersion: CollectionRouteHandlerWithID = async ({
}) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const draft = searchParams.get('draft')
const id = sanitizeCollectionID({
id: incomingID,
@@ -26,7 +25,6 @@ export const restoreVersion: CollectionRouteHandlerWithID = async ({
id,
collection,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: draft === 'true' ? true : undefined,
req,
})

View File

@@ -9,12 +9,10 @@ import { headersWithCors } from '../../../utilities/headersWithCors.js'
export const restoreVersion: GlobalRouteHandlerWithID = async ({ id, globalConfig, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const draft = searchParams.get('draft')
const doc = await restoreVersionOperationGlobal({
id,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: draft === 'true' ? true : undefined,
globalConfig,
req,
})

View File

@@ -1,9 +1,9 @@
@import 'styles';
@import './styles.scss';
@import './toasts.scss';
@import './colors.scss';
:root {
--base-px: 20;
--base-px: 25;
--base-body-size: 13;
--base: calc((var(--base-px) / var(--base-body-size)) * 1rem);
@@ -21,7 +21,6 @@
--theme-baseline-body-size: #{$baseline-body-size};
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
--font-serif: Georgia, 'Bitstream Charter', 'Charis SIL', Utopia, 'URW Bookman L', serif;
--font-mono: monospace;
--style-radius-s: #{$style-radius-s};
@@ -68,6 +67,12 @@ html {
@extend %body;
background: var(--theme-bg);
-webkit-font-smoothing: antialiased;
opacity: 0;
&[data-theme='dark'],
&[data-theme='light'] {
opacity: initial;
}
&[data-theme='dark'] {
--theme-bg: var(--theme-elevation-0);
@@ -106,12 +111,12 @@ body {
}
::selection {
background: var(--color-success-250);
background: var(--theme-success-500);
color: var(--theme-base-800);
}
::-moz-selection {
background: var(--color-success-250);
background: var(--theme-success-500);
color: var(--theme-base-800);
}

View File

@@ -1,59 +0,0 @@
@import 'vars';
@import 'queries';
.Toastify {
.Toastify__toast-container {
left: base(5);
transform: none;
right: base(5);
width: auto;
}
.Toastify__toast {
padding: base(0.5);
border-radius: $style-radius-m;
font-weight: 600;
}
.Toastify__close-button {
align-self: center;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
.Toastify__toast--success {
color: var(--color-success-900);
background: var(--color-success-500);
.Toastify__progress-bar {
background-color: var(--color-success-900);
}
}
.Toastify__close-button--success {
color: var(--color-success-900);
}
.Toastify__toast--error {
background: var(--theme-error-500);
color: #fff;
.Toastify__progress-bar {
background-color: #fff;
}
}
.Toastify__close-button--light {
color: inherit;
}
@include mid-break {
.Toastify__toast-container {
left: $baseline;
right: $baseline;
}
}
}

View File

@@ -1,50 +1,42 @@
@import './styles.scss';
.payload-toast-container {
padding: 0;
margin: 0;
.payload-toast-close-button {
position: absolute;
order: 3;
left: unset;
inset-inline-end: base(0.5);
top: 50%;
transform: translateY(-50%);
color: var(--theme-elevation-600);
right: 0.5rem;
top: 1.55rem;
color: var(--theme-elevation-400);
background: unset;
border: none;
svg {
width: base(0.75);
height: base(0.75);
}
display: flex;
width: 1.25rem;
height: 1.25rem;
justify-content: center;
align-items: center;
&:hover {
color: var(--theme-elevation-250);
background: none;
}
svg {
width: 2rem;
height: 2rem;
}
[dir='RTL'] & {
right: unset;
left: 0.5rem;
}
}
.toast-title {
line-height: base(1);
}
.payload-toast-item {
padding: base(0.5);
color: var(--theme-elevation-800);
padding: 1rem 2.5rem 1rem 1rem;
color: var(--theme-text);
font-style: normal;
font-weight: 600;
display: flex;
gap: 1rem;
align-items: center;
width: 100%;
border-radius: 4px;
border-radius: 0.15rem;
border: 1px solid var(--theme-border-color);
background: var(--theme-input-bg);
box-shadow:
@@ -53,7 +45,6 @@
.toast-content {
transition: opacity 100ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
width: 100%;
}
&[data-front='false'] {
@@ -69,72 +60,51 @@
}
.toast-icon {
width: base(1);
height: base(1);
margin: 0;
display: flex;
align-items: center;
justify-content: center;
& > * {
width: base(1.2);
height: base(1.2);
svg {
width: 2.4rem;
height: 2.4rem;
}
}
&.toast-warning {
color: var(--theme-warning-800);
border-color: var(--theme-warning-150);
background-color: var(--theme-warning-50);
.payload-toast-close-button {
color: var(--theme-warning-600);
&:hover {
color: var(--theme-warning-250);
}
}
border-color: var(--theme-warning-200);
background-color: var(--theme-warning-100);
}
&.toast-error {
color: var(--theme-error-800);
border-color: var(--theme-error-150);
background-color: var(--theme-error-50);
.payload-toast-close-button {
color: var(--theme-error-600);
&:hover {
color: var(--theme-error-250);
}
}
border-color: var(--theme-error-300);
background-color: var(--theme-error-150);
}
&.toast-success {
color: var(--theme-success-800);
border-color: var(--theme-success-150);
background-color: var(--theme-success-50);
.payload-toast-close-button {
color: var(--theme-success-600);
&:hover {
color: var(--theme-success-250);
}
}
border-color: var(--theme-success-200);
background-color: var(--theme-success-100);
}
&.toast-info {
color: var(--theme-elevation-800);
border-color: var(--theme-elevation-150);
background-color: var(--theme-elevation-50);
border-color: var(--theme-elevation-250);
background-color: var(--theme-elevation-100);
}
.payload-toast-close-button {
color: var(--theme-elevation-600);
[data-theme='light'] & {
&.toast-warning {
border-color: var(--theme-warning-550);
background-color: var(--theme-warning-100);
}
&:hover {
color: var(--theme-elevation-250);
}
&.toast-error {
border-color: var(--theme-error-200);
background-color: var(--theme-error-50);
}
&.toast-success {
border-color: var(--theme-success-550);
background-color: var(--theme-success-50);
}
&.toast-info {
border-color: var(--theme-border-color);
background-color: var(--theme-elevation-50);
}
}
}

View File

@@ -15,10 +15,17 @@
font-weight: 500;
}
%jumbo {
font-size: base(2.5);
line-height: 1;
margin: 0 0 base(2);
}
%h1 {
margin: 0 0 base(1);
font-size: base(1.6);
line-height: base(1.8);
font-size: base(2);
line-height: 1.15;
letter-spacing: -1px;
@include small-break {
letter-spacing: -0.5px;
@@ -28,8 +35,9 @@
%h2 {
margin: 0 0 base(1);
font-size: base(1.3);
line-height: base(1.6);
font-size: base(1.25);
line-height: 1.15;
letter-spacing: -0.5px;
@include small-break {
font-size: base(0.85);
@@ -38,8 +46,9 @@
%h3 {
margin: 0 0 base(1);
font-size: base(1);
line-height: base(1.2);
font-size: base(0.925);
line-height: 1.25;
letter-spacing: -0.5px;
@include small-break {
font-size: base(0.65);
@@ -49,27 +58,27 @@
%h4 {
margin: 0 0 $baseline;
font-size: base(0.8);
line-height: base(1);
font-size: base(0.75);
line-height: 1.5;
letter-spacing: -0.375px;
}
%h5 {
margin: 0;
font-size: base(0.65);
line-height: base(0.8);
font-size: base(0.5625);
line-height: 1.5;
}
%h6 {
margin: 0;
font-size: base(0.6);
line-height: base(0.8);
font-size: base(0.5);
line-height: 1.5;
}
%small {
margin: 0;
font-size: 12px;
line-height: 20px;
font-size: 11px;
line-height: 1.5;
}
/////////////////////////////

View File

@@ -13,7 +13,7 @@ $breakpoint-l-width: 1440px !default;
// BASELINE GRID
//////////////////////////////
$baseline-px: 20px !default;
$baseline-px: 25px !default;
$baseline-body-size: 13px !default;
$baseline: math.div($baseline-px, $baseline-body-size) + rem;
@@ -40,7 +40,7 @@ $color-purple: #f3ddf3 !default;
$style-radius-s: 3px !default;
$style-radius-m: 4px !default;
$style-radius-l: 8px !default;
$style-radius-l: 9px !default;
$style-stroke-width: 1px !default;
$style-stroke-width-s: 1px !default;
@@ -50,8 +50,8 @@ $style-stroke-width-m: 2px !default;
// MISC
//////////////////////////////
$top-header-offset: calc(base(1) - 1px);
$top-header-offset-m: base(3);
$top-header-offset: calc(var(--base) - 1px);
$top-header-offset-m: calc(var(--base) * 3);
$focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
//////////////////////////////
@@ -59,19 +59,41 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
//////////////////////////////
@mixin shadow-sm {
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
box-shadow:
0 2px 3px 0 rgba(0, 2, 4, 0.05),
0 10px 4px -8px rgba(0, 2, 4, 0.02);
}
@mixin shadow-m {
box-shadow: 0 4px 8px -3px rgba(0, 0, 0, 0.1);
box-shadow:
0 0 30px 0 rgb(0 2 4 / 12%),
0 30px 25px -8px rgb(0 2 4 / 10%);
}
@mixin shadow-lg {
box-shadow: 0 -2px 16px -2px rgba(0, 0, 0, 0.2);
box-shadow:
0 20px 35px -10px rgba(0, 2, 4, 0.2),
0 6px 4px -4px rgba(0, 2, 4, 0.02);
}
@mixin shadow-lg-top {
box-shadow: 0 2px 16px -2px rgba(0, 0, 0, 0.2);
box-shadow:
0 -20px 35px -10px rgba(0, 2, 4, 0.2),
0 -6px 4px -4px rgba(0, 2, 4, 0.02);
}
@mixin shadow {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.07);
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
@mixin inputShadowActive {
box-shadow:
0 2px 3px 0 rgba(0, 2, 4, 0.16),
0 6px 4px -4px rgba(0, 2, 4, 0.13);
}
@mixin inputShadow {
@@ -79,7 +101,15 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
&:not(:disabled) {
&:hover {
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.2);
box-shadow:
0 2px 3px 0 rgba(0, 2, 4, 0.13),
0 6px 4px -4px rgba(0, 2, 4, 0.1);
}
&:active,
&:focus-within,
&:focus {
@include inputShadowActive;
}
}
}
@@ -117,33 +147,19 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
@include blur-bg(var(--theme-bg), 0.3);
}
@mixin readOnly {
background: var(--theme-elevation-100);
color: var(--theme-elevation-400);
box-shadow: none;
&:hover {
border-color: var(--theme-elevation-150);
box-shadow: none;
}
}
@mixin formInput() {
@include inputShadow;
font-family: var(--font-body);
width: 100%;
border: 1px solid var(--theme-elevation-150);
border-radius: var(--style-radius-s);
background: var(--theme-input-bg);
color: var(--theme-elevation-800);
border-radius: 0;
font-size: 1rem;
height: base(2);
line-height: base(1);
padding: base(0.4) base(0.75);
padding: base(0.5) base(0.75);
-webkit-appearance: none;
transition-property: border, box-shadow;
transition-duration: 100ms;
transition-timing-function: cubic-bezier(0, 0.2, 0.2, 1);
&[data-rtl='true'] {
direction: rtl;
@@ -173,7 +189,12 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
}
&:disabled {
@include readOnly;
background: var(--theme-elevation-200);
color: var(--theme-elevation-450);
&:hover {
border-color: var(--theme-elevation-150);
}
}
}

View File

@@ -1,5 +1,5 @@
'use client'
import type { FormState, LoginWithUsernameOptions } from 'payload'
import type { FormState } from 'payload'
import {
ConfirmPasswordField,
@@ -15,13 +15,14 @@ import {
import { getFormState } from '@payloadcms/ui/shared'
import React from 'react'
import { EmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
import { LoginField } from '../Login/LoginField/index.js'
export const CreateFirstUserClient: React.FC<{
initialState: FormState
loginWithUsername?: LoginWithUsernameOptions | false
loginType: 'email' | 'emailOrUsername' | 'username'
requireEmail?: boolean
userSlug: string
}> = ({ initialState, loginWithUsername, userSlug }) => {
}> = ({ initialState, loginType, requireEmail = true, userSlug }) => {
const { getFieldMap } = useComponentMap()
const {
@@ -34,17 +35,18 @@ export const CreateFirstUserClient: React.FC<{
const fieldMap = getFieldMap({ collectionSlug: userSlug })
const onChange: FormProps['onChange'][0] = React.useCallback(
async ({ formState: prevFormState }) =>
getFormState({
async ({ formState: prevFormState }) => {
return getFormState({
apiRoute,
body: {
collectionSlug: userSlug,
formState: prevFormState,
operation: 'create',
schemaPath: `_${userSlug}.auth`,
schemaPath: userSlug,
},
serverURL,
}),
})
},
[apiRoute, userSlug, serverURL],
)
@@ -57,23 +59,25 @@ export const CreateFirstUserClient: React.FC<{
redirect={admin}
validationOperation="create"
>
<EmailAndUsernameFields loginWithUsername={loginWithUsername} />
{['emailOrUsername', 'username'].includes(loginType) && <LoginField type="username" />}
{['email', 'emailOrUsername'].includes(loginType) && (
<LoginField required={requireEmail} type="email" />
)}
<PasswordField
autoComplete="off"
label={t('authentication:newPassword')}
name="password"
path="password"
required
/>
<ConfirmPasswordField />
<RenderFields
fieldMap={fieldMap}
forceRender
operation="create"
path=""
readOnly={false}
schemaPath={userSlug}
/>
<FormSubmit size="large">{t('general:create')}</FormSubmit>
<FormSubmit>{t('general:create')}</FormSubmit>
</Form>
)
}

View File

@@ -1,10 +1,10 @@
import type { AdminViewProps } from 'payload'
import type { AdminViewProps, Field } from 'payload'
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import React from 'react'
import type { LoginFieldProps } from '../Login/LoginField/index.js'
import { getDocumentData } from '../Document/getDocumentData.js'
import { CreateFirstUserClient } from './index.client.js'
import './index.scss'
@@ -12,7 +12,6 @@ export { generateCreateFirstUserMetadata } from './meta.js'
export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageResult }) => {
const {
locale,
req,
req: {
payload: {
@@ -27,12 +26,50 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug)
const { auth: authOptions } = collectionConfig
const loginWithUsername = authOptions.loginWithUsername
const loginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
const emailRequired = loginWithUsername && loginWithUsername.requireEmail
const { formState } = await getDocumentData({
collectionConfig,
locale,
let loginType: LoginFieldProps['type'] = loginWithUsername ? 'username' : 'email'
if (loginWithUsername && (loginWithUsername.allowEmailLogin || loginWithUsername.requireEmail)) {
loginType = 'emailOrUsername'
}
const emailField = {
name: 'email',
type: 'email',
label: req.t('general:emailAddress'),
required: emailRequired ? true : false,
}
const usernameField = {
name: 'username',
type: 'text',
label: req.t('authentication:username'),
required: true,
}
const fields = [
...(loginWithUsername ? [usernameField] : []),
...(emailRequired || loginWithEmail ? [emailField] : []),
{
name: 'password',
type: 'text',
label: req.t('general:password'),
required: true,
},
{
name: 'confirm-password',
type: 'text',
label: req.t('authentication:confirmPassword'),
required: true,
},
]
const formState = await buildStateFromSchema({
fieldSchema: fields as Field[],
operation: 'create',
preferences: { fields: {} },
req,
schemaPath: `_${collectionConfig.slug}.auth`,
})
return (
@@ -41,7 +78,8 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
<p>{req.t('authentication:beginCreateFirstUser')}</p>
<CreateFirstUserClient
initialState={formState}
loginWithUsername={loginWithUsername}
loginType={loginType}
requireEmail={emailRequired}
userSlug={userSlug}
/>
</div>

View File

@@ -26,9 +26,13 @@
padding: 0;
margin: 0;
list-style: none;
display: flex;
gap: var(--gap);
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
flex-wrap: wrap;
li {
width: calc(100% / var(--cols) - var(--gap) / var(--cols) * (var(--cols) - 1));
}
.card {
height: 100%;
@@ -45,18 +49,10 @@
}
@include small-break {
--cols: 2;
--cols: 1;
&__wrap {
gap: var(--base);
}
&__card-list {
gap: base(0.4);
}
}
@include extra-small-break {
--cols: 1;
}
}

View File

@@ -15,11 +15,8 @@ export const getDocumentData = async (args: {
id?: number | string
locale: Locale
req: PayloadRequest
schemaPath?: string
}): Promise<Data> => {
const { id, collectionConfig, globalConfig, locale, req, schemaPath: schemaPathFromProps } = args
const schemaPath = schemaPathFromProps || collectionConfig?.slug || globalConfig?.slug
const { id, collectionConfig, globalConfig, locale, req } = args
try {
const formState = await buildFormState({
@@ -31,7 +28,7 @@ export const getDocumentData = async (args: {
globalSlug: globalConfig?.slug,
locale: locale?.code,
operation: (collectionConfig && id) || globalConfig ? 'update' : 'create',
schemaPath,
schemaPath: collectionConfig?.slug || globalConfig?.slug,
},
},
})

View File

@@ -78,7 +78,7 @@ export const APIKey: React.FC<{ enabled: boolean; readOnly?: boolean }> = ({
if (!apiKeyValue && enabled) {
setValue(initialAPIKey)
}
if (!enabled && apiKeyValue) {
if (!enabled) {
setValue(null)
}
}, [apiKeyValue, enabled, setValue, initialAPIKey])
@@ -100,7 +100,6 @@ export const APIKey: React.FC<{ enabled: boolean; readOnly?: boolean }> = ({
<div className={[fieldBaseClass, 'api-key', 'read-only'].filter(Boolean).join(' ')}>
<FieldLabel CustomLabel={APIKeyLabel} htmlFor={path} />
<input
aria-label="API Key"
className={highlightedField ? 'highlight' : undefined}
disabled
id="apiKey"

View File

@@ -4,7 +4,9 @@ import {
Button,
CheckboxField,
ConfirmPasswordField,
EmailField,
PasswordField,
TextField,
useAuth,
useConfig,
useDocumentInfo,
@@ -17,7 +19,6 @@ import { toast } from 'sonner'
import type { Props } from './types.js'
import { EmailAndUsernameFields } from '../../../../elements/EmailAndUsername/index.js'
import { APIKey } from './APIKey.js'
import './index.scss'
@@ -33,8 +34,6 @@ export const Auth: React.FC<Props> = (props) => {
operation,
readOnly,
requirePassword,
setSchemaPath,
setValidateBeforeSubmit,
useAPIKey,
username,
verify,
@@ -43,7 +42,6 @@ export const Auth: React.FC<Props> = (props) => {
const { permissions } = useAuth()
const [changingPassword, setChangingPassword] = useState(requirePassword)
const enableAPIKey = useFormFields(([fields]) => (fields && fields?.enableAPIKey) || null)
const forceOpenChangePassword = useFormFields(([fields]) => (fields && fields?.password) || null)
const dispatchFields = useFormFields((reducer) => reducer[1])
const modified = useFormModified()
const { i18n, t } = useTranslation()
@@ -72,32 +70,15 @@ export const Auth: React.FC<Props> = (props) => {
}, [permissions, collectionSlug])
const handleChangePassword = useCallback(
(showPasswordFields: boolean) => {
if (showPasswordFields) {
setValidateBeforeSubmit(true)
setSchemaPath(`_${collectionSlug}.auth`)
dispatchFields({
type: 'UPDATE',
errorMessage: t('validation:required'),
path: 'password',
valid: false,
})
dispatchFields({
type: 'UPDATE',
errorMessage: t('validation:required'),
path: 'confirm-password',
valid: false,
})
} else {
setValidateBeforeSubmit(false)
setSchemaPath(collectionSlug)
(state: boolean) => {
if (!state) {
dispatchFields({ type: 'REMOVE', path: 'password' })
dispatchFields({ type: 'REMOVE', path: 'confirm-password' })
}
setChangingPassword(showPasswordFields)
setChangingPassword(state)
},
[dispatchFields, t, collectionSlug, setSchemaPath, setValidateBeforeSubmit],
[dispatchFields],
)
const unlock = useCallback(async () => {
@@ -118,7 +99,7 @@ export const Auth: React.FC<Props> = (props) => {
} else {
toast.error(t('authentication:failedToUnlock'))
}
}, [i18n, serverURL, api, collectionSlug, email, username, t, loginWithUsername])
}, [i18n, serverURL, api, collectionSlug, email, username, t])
useEffect(() => {
if (!modified) {
@@ -132,43 +113,61 @@ export const Auth: React.FC<Props> = (props) => {
const disabled = readOnly || isInitializing
const showPasswordFields = changingPassword || forceOpenChangePassword
return (
<div className={[baseClass, className].filter(Boolean).join(' ')}>
{!disableLocalStrategy && (
<React.Fragment>
<EmailAndUsernameFields loginWithUsername={loginWithUsername} />
{(showPasswordFields || requirePassword) && (
{Boolean(loginWithUsername) && (
<TextField
disabled={disabled}
label={t('authentication:username')}
name="username"
readOnly={readOnly}
required
/>
)}
{(!loginWithUsername ||
loginWithUsername?.allowEmailLogin ||
loginWithUsername?.requireEmail) && (
<EmailField
autoComplete="email"
disabled={disabled}
label={t('general:email')}
name="email"
readOnly={readOnly}
required={!loginWithUsername || loginWithUsername?.requireEmail}
/>
)}
{(changingPassword || requirePassword) && (
<div className={`${baseClass}__changing-password`}>
<PasswordField
autoComplete="off"
disabled={disabled}
label={t('authentication:newPassword')}
name="password"
path="password"
required
/>
<ConfirmPasswordField disabled={readOnly} />
</div>
)}
<div className={`${baseClass}__controls`}>
{showPasswordFields && !requirePassword && (
{changingPassword && !requirePassword && (
<Button
buttonStyle="secondary"
disabled={disabled}
onClick={() => handleChangePassword(false)}
size="medium"
size="small"
>
{t('general:cancel')}
</Button>
)}
{!showPasswordFields && !requirePassword && (
{!changingPassword && !requirePassword && (
<Button
buttonStyle="secondary"
disabled={disabled}
id="change-password"
onClick={() => handleChangePassword(true)}
size="medium"
size="small"
>
{t('authentication:changePassword')}
</Button>
@@ -178,7 +177,7 @@ export const Auth: React.FC<Props> = (props) => {
buttonStyle="secondary"
disabled={disabled}
onClick={() => void unlock()}
size="medium"
size="small"
>
{t('authentication:forceUnlock')}
</Button>

View File

@@ -9,8 +9,6 @@ export type Props = {
operation: 'create' | 'update'
readOnly: boolean
requirePassword?: boolean
setSchemaPath: (path: string) => void
setValidateBeforeSubmit: (validate: boolean) => void
useAPIKey?: boolean
username: string
verify?: VerifyConfig | boolean

View File

@@ -8,8 +8,8 @@
}
&__auth {
margin-bottom: base(1.6);
border-radius: var(--style-radius-s);
margin-bottom: calc(var(--base) * 2);
margin-top: calc(var(--base) * 0.5);
}
@include small-break {

View File

@@ -17,7 +17,7 @@ import {
} from '@payloadcms/ui'
import { formatAdminURL, getFormState } from '@payloadcms/ui/shared'
import { useRouter, useSearchParams } from 'next/navigation.js'
import React, { Fragment, useCallback, useState } from 'react'
import React, { Fragment, useCallback } from 'react'
import { LeaveWithoutSaving } from '../../../elements/LeaveWithoutSaving/index.js'
import { Auth } from './Auth/index.js'
@@ -102,9 +102,6 @@ export const DefaultEditView: React.FC = () => {
const classes = [baseClass, id && `${baseClass}--is-editing`].filter(Boolean).join(' ')
const [schemaPath, setSchemaPath] = React.useState(entitySlug)
const [validateBeforeSubmit, setValidateBeforeSubmit] = useState(false)
const onSave = useCallback(
(json) => {
reportUpdate({
@@ -161,6 +158,7 @@ export const DefaultEditView: React.FC = () => {
const onChange: FormProps['onChange'][0] = useCallback(
async ({ formState: prevFormState }) => {
const docPreferences = await getDocPreferences()
return getFormState({
apiRoute,
body: {
@@ -170,12 +168,12 @@ export const DefaultEditView: React.FC = () => {
formState: prevFormState,
globalSlug,
operation,
schemaPath,
schemaPath: entitySlug,
},
serverURL,
})
},
[apiRoute, collectionSlug, schemaPath, getDocPreferences, globalSlug, id, operation, serverURL],
[serverURL, apiRoute, id, operation, entitySlug, collectionSlug, globalSlug, getDocPreferences],
)
return (
@@ -184,7 +182,7 @@ export const DefaultEditView: React.FC = () => {
<Form
action={action}
className={`${baseClass}__form`}
disableValidationOnSubmit={!validateBeforeSubmit}
disableValidationOnSubmit
disabled={isInitializing || !hasSavePermission}
initialState={!isInitializing && initialState}
isInitializing={isInitializing}
@@ -233,8 +231,6 @@ export const DefaultEditView: React.FC = () => {
operation={operation}
readOnly={!hasSavePermission}
requirePassword={!id}
setSchemaPath={setSchemaPath}
setValidateBeforeSubmit={setValidateBeforeSubmit}
useAPIKey={auth.useAPIKey}
username={data?.username}
verify={auth.verify}
@@ -259,7 +255,7 @@ export const DefaultEditView: React.FC = () => {
docPermissions={docPermissions}
fieldMap={fieldMap}
readOnly={!hasSavePermission}
schemaPath={schemaPath}
schemaPath={entitySlug}
/>
{AfterDocument}
</Form>

View File

@@ -51,7 +51,7 @@ export const ForgotPasswordView: React.FC<AdminViewProps> = ({ initPageResult })
/>
</p>
<br />
<Button Link={Link} buttonStyle="secondary" el="link" size="large" to={adminRoute}>
<Button Link={Link} buttonStyle="secondary" el="link" to={adminRoute}>
{i18n.t('general:backToDashboard')}
</Button>
</Fragment>

View File

@@ -2,6 +2,7 @@
.collection-list {
width: 100%;
margin-top: base(0.5);
&__wrap {
padding-bottom: var(--spacing-view-bottom);
@@ -13,7 +14,7 @@
&__header {
display: flex;
align-items: center;
align-items: flex-end;
flex-wrap: wrap;
gap: base(0.75);
@@ -27,12 +28,14 @@
.pill {
position: relative;
top: -14px;
margin: 0;
}
}
&__sub-header {
flex-basis: 100%;
margin-top: base(0.25);
}
.table {
@@ -54,7 +57,7 @@
#heading-_select,
.cell-_select {
min-width: unset;
width: base(1);
width: auto;
}
}
}

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