chore: pulls mongodb from main
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
const nextJest = require('next/jest.js')
|
||||
// const nextJest = require('next/jest.js')
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
|
||||
dir: './',
|
||||
})
|
||||
// const createJestConfig = nextJest({
|
||||
// // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
|
||||
// dir: './',
|
||||
// })
|
||||
|
||||
const customJestConfig = {
|
||||
globalSetup: './test/jest.setup.ts',
|
||||
@@ -12,10 +12,14 @@ const customJestConfig = {
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
'<rootDir>/packages/payload/src/bundlers/mocks/fileMock.js',
|
||||
},
|
||||
// testEnvironment: 'node',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['<rootDir>/packages/payload/src/**/*.spec.ts', '<rootDir>/test/**/*int.spec.ts'],
|
||||
testTimeout: 90000,
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': ['@swc/jest'],
|
||||
},
|
||||
verbose: true,
|
||||
}
|
||||
|
||||
module.exports = createJestConfig(customJestConfig)
|
||||
// module.exports = createJestConfig(customJestConfig)
|
||||
module.exports = customJestConfig
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
const path = require('path')
|
||||
|
||||
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
})
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
@@ -13,6 +17,9 @@ const nextConfig = {
|
||||
},
|
||||
},
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
webpack: (config) => {
|
||||
return {
|
||||
...config,
|
||||
@@ -53,4 +60,4 @@ const nextConfig = {
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
module.exports = withBundleAnalyzer(nextConfig)
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^14.1.0",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@playwright/test": "1.40.1",
|
||||
"@swc/cli": "^0.1.62",
|
||||
@@ -81,7 +82,7 @@
|
||||
"lexical": "0.12.5",
|
||||
"lint-staged": "^14.0.1",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "8.13.0",
|
||||
"mongodb-memory-server": "^9",
|
||||
"next": "14.1.1-canary.26",
|
||||
"node-fetch": "2.6.12",
|
||||
"node-mocks-http": "^1.14.1",
|
||||
@@ -126,7 +127,9 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"graphql": "^16.8.1"
|
||||
"graphql": "^16.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const mongooseAdapter = () => ({})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.1.0",
|
||||
"version": "1.4.1",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -11,13 +11,6 @@
|
||||
"url": "https://payloadcms.com"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm build:swc && pnpm build:types",
|
||||
@@ -27,18 +20,17 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"bson-ext": "^4.0.3",
|
||||
"bson-objectid": "2.0.4",
|
||||
"deepmerge": "4.3.1",
|
||||
"get-port": "5.1.1",
|
||||
"mongoose": "7.6.8",
|
||||
"mongoose": "6.12.3",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
"prompts": "2.4.2",
|
||||
"http-status": "1.6.2",
|
||||
"uuid": "9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"mongodb-memory-server": "8.13.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -27,6 +27,13 @@ export const connect: Connect = async function connect(this: MongooseAdapter, pa
|
||||
try {
|
||||
this.connection = (await mongoose.connect(urlToConnect, connectionOptions)).connection
|
||||
|
||||
const client = this.connection.getClient()
|
||||
|
||||
if (!client.options.replicaSet) {
|
||||
this.transactionOptions = false
|
||||
this.beginTransaction = undefined
|
||||
}
|
||||
|
||||
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
|
||||
this.payload.logger.info('---- DROPPING DATABASE ----')
|
||||
await mongoose.connection.dropDatabase()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { Create } from 'payload/database'
|
||||
import type { Document, PayloadRequest } from 'payload/types'
|
||||
|
||||
import { ValidationError } from 'payload/errors'
|
||||
|
||||
import type { MongooseAdapter } from '.'
|
||||
|
||||
import handleError from './utilities/handleError'
|
||||
import { withSession } from './withSession'
|
||||
|
||||
export const create: Create = async function create(
|
||||
@@ -17,18 +16,7 @@ export const create: Create = async function create(
|
||||
try {
|
||||
;[doc] = await Model.create([data], options)
|
||||
} catch (error) {
|
||||
// Handle uniqueness error from MongoDB
|
||||
throw error.code === 11000 && error.keyValue
|
||||
? new ValidationError(
|
||||
[
|
||||
{
|
||||
field: Object.keys(error.keyValue)[0],
|
||||
message: req.t('error:valueMustBeUnique'),
|
||||
},
|
||||
],
|
||||
req.t,
|
||||
)
|
||||
: error
|
||||
handleError(error, req)
|
||||
}
|
||||
|
||||
// doc.toJSON does not do stuff like converting ObjectIds to string, or date strings to date objects. That's why we use JSON.parse/stringify here
|
||||
|
||||
@@ -49,6 +49,7 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
||||
],
|
||||
},
|
||||
{ $unset: { latest: 1 } },
|
||||
options,
|
||||
)
|
||||
|
||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
// import type { CreateMigration } from 'payload/database'
|
||||
import type { CreateMigration } from 'payload/database'
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
@@ -18,7 +18,11 @@ ${downSQL ?? ` // Migration code`}
|
||||
};
|
||||
`
|
||||
|
||||
export const createMigration = async function createMigration({ file, migrationName, payload }) {
|
||||
export const createMigration: CreateMigration = async function createMigration({
|
||||
file,
|
||||
migrationName,
|
||||
payload,
|
||||
}) {
|
||||
const dir = payload.db.migrationDir
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir)
|
||||
@@ -28,7 +32,7 @@ export const createMigration = async function createMigration({ file, migrationN
|
||||
|
||||
// Check for predefined migration.
|
||||
// Either passed in via --file or prefixed with @payloadcms/db-mongodb/
|
||||
if (file || migrationName.startsWith('@payloadcms/db-mongodb/')) {
|
||||
if (file || migrationName?.startsWith('@payloadcms/db-mongodb/')) {
|
||||
if (!file) file = migrationName
|
||||
|
||||
const predefinedMigrationName = file.replace('@payloadcms/db-mongodb/', '')
|
||||
@@ -37,8 +41,8 @@ export const createMigration = async function createMigration({ file, migrationN
|
||||
|
||||
// Check if predefined migration exists
|
||||
if (fs.existsSync(cleanPath)) {
|
||||
// const { down, up } = require(cleanPath)
|
||||
// migrationFileContent = migrationTemplate(up, down)
|
||||
const { down, up } = require(cleanPath)
|
||||
migrationFileContent = migrationTemplate(up, down)
|
||||
} else {
|
||||
payload.logger.error({
|
||||
msg: `Canned migration ${predefinedMigrationName} not found.`,
|
||||
@@ -55,8 +59,8 @@ export const createMigration = async function createMigration({ file, migrationN
|
||||
|
||||
const timestamp = `${formattedDate}_${formattedTime}`
|
||||
|
||||
const formattedName = migrationName.replace(/\W/g, '_')
|
||||
const fileName = `${timestamp}_${formattedName}.ts`
|
||||
const formattedName = migrationName?.replace(/\W/g, '_')
|
||||
const fileName = migrationName ? `${timestamp}_${formattedName}.ts` : `${timestamp}_migration.ts`
|
||||
const filePath = `${dir}/${fileName}`
|
||||
fs.writeFileSync(filePath, migrationFileContent)
|
||||
payload.logger.info({ msg: `Migration created at ${filePath}` })
|
||||
|
||||
@@ -57,6 +57,7 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
],
|
||||
},
|
||||
{ $unset: { latest: 1 } },
|
||||
options,
|
||||
)
|
||||
|
||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||
|
||||
@@ -55,21 +55,30 @@ export const find: Find = async function find(
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding a hint.
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||
// the correct indexed field
|
||||
paginationOptions.useCustomCountFn = () => {
|
||||
return Promise.resolve(
|
||||
Model.countDocuments(query, {
|
||||
...options,
|
||||
hint: { _id: 1 },
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (limit > 0) {
|
||||
if (limit >= 0) {
|
||||
paginationOptions.limit = limit
|
||||
// limit must also be set here, it's ignored when pagination is false
|
||||
paginationOptions.options.limit = limit
|
||||
|
||||
// Disable pagination if limit is 0
|
||||
if (limit === 0) {
|
||||
paginationOptions.pagination = false
|
||||
}
|
||||
}
|
||||
|
||||
const result = await Model.paginate(query, paginationOptions)
|
||||
|
||||
@@ -74,21 +74,30 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding a hint.
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||
// the correct indexed field
|
||||
paginationOptions.useCustomCountFn = () => {
|
||||
return Promise.resolve(
|
||||
Model.countDocuments(query, {
|
||||
...options,
|
||||
hint: { _id: 1 },
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (limit > 0) {
|
||||
if (limit >= 0) {
|
||||
paginationOptions.limit = limit
|
||||
// limit must also be set here, it's ignored when pagination is false
|
||||
paginationOptions.options.limit = limit
|
||||
|
||||
// Disable pagination if limit is 0
|
||||
if (limit === 0) {
|
||||
paginationOptions.pagination = false
|
||||
}
|
||||
}
|
||||
|
||||
const result = await Model.paginate(query, paginationOptions)
|
||||
|
||||
@@ -63,7 +63,6 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
lean: true,
|
||||
leanWithId: true,
|
||||
limit,
|
||||
offset: skip || 0,
|
||||
options,
|
||||
page,
|
||||
pagination,
|
||||
@@ -71,21 +70,30 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding a hint.
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||
// the correct indexed field
|
||||
paginationOptions.useCustomCountFn = () => {
|
||||
return Promise.resolve(
|
||||
Model.countDocuments(query, {
|
||||
...options,
|
||||
hint: { _id: 1 },
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (limit > 0) {
|
||||
if (limit >= 0) {
|
||||
paginationOptions.limit = limit
|
||||
// limit must also be set here, it's ignored when pagination is false
|
||||
paginationOptions.options.limit = limit
|
||||
|
||||
// Disable pagination if limit is 0
|
||||
if (limit === 0) {
|
||||
paginationOptions.pagination = false
|
||||
}
|
||||
}
|
||||
|
||||
const result = await Model.paginate(query, paginationOptions)
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import type { TransactionOptions } from 'mongodb'
|
||||
import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
|
||||
import type { Payload } from 'payload'
|
||||
import type { BaseDatabaseAdapter, DatabaseAdapterObj } from 'payload/database'
|
||||
import type { BaseDatabaseAdapter } from 'payload/database'
|
||||
|
||||
import fs from 'fs'
|
||||
import mongoose from 'mongoose'
|
||||
import path from 'path'
|
||||
import { createDatabaseAdapter } from 'payload/database'
|
||||
|
||||
export type { MigrateDownArgs, MigrateUpArgs } from './types'
|
||||
|
||||
import type { CollectionModel, GlobalModel } from './types'
|
||||
|
||||
import { connect } from './connect'
|
||||
@@ -37,6 +36,9 @@ import { updateGlobalVersion } from './updateGlobalVersion'
|
||||
import { updateOne } from './updateOne'
|
||||
import { updateVersion } from './updateVersion'
|
||||
|
||||
export type { MigrateDownArgs, MigrateUpArgs } from './types'
|
||||
import type { DatabaseAdapterObj } from 'payload/database'
|
||||
|
||||
export interface Args {
|
||||
/** Set to false to disable auto-pluralization of collection names, Defaults to true */
|
||||
autoPluralization?: boolean
|
||||
@@ -48,6 +50,7 @@ export interface Args {
|
||||
/** 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
|
||||
migrationDir?: string
|
||||
transactionOptions?: TransactionOptions | false
|
||||
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
|
||||
url: false | string
|
||||
}
|
||||
@@ -76,7 +79,8 @@ declare module 'payload' {
|
||||
connection: Connection
|
||||
globals: GlobalModel
|
||||
mongoMemoryServer: any
|
||||
// sessions: Record<number | string, ClientSession>
|
||||
sessions: Record<number | string, ClientSession>
|
||||
transactionOptions: TransactionOptions
|
||||
versions: {
|
||||
[slug: string]: CollectionModel
|
||||
}
|
||||
@@ -88,8 +92,9 @@ export function mongooseAdapter({
|
||||
connectOptions,
|
||||
disableIndexHints = false,
|
||||
migrationDir: migrationDirArg,
|
||||
transactionOptions = {},
|
||||
url,
|
||||
}: Args): DatabaseAdapterObj<MongooseAdapter> {
|
||||
}: Args): DatabaseAdapterObj {
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = findMigrationDir(migrationDirArg)
|
||||
mongoose.set('strictQuery', false)
|
||||
@@ -106,11 +111,12 @@ export function mongooseAdapter({
|
||||
globals: undefined,
|
||||
mongoMemoryServer: undefined,
|
||||
sessions: {},
|
||||
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
|
||||
url,
|
||||
versions: {},
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction,
|
||||
beginTransaction: transactionOptions ? beginTransaction : undefined,
|
||||
commitTransaction,
|
||||
connect,
|
||||
create,
|
||||
|
||||
@@ -48,7 +48,7 @@ export const init: Init = async function init(this: MongooseAdapter) {
|
||||
versionModelName,
|
||||
versionSchema,
|
||||
this.autoPluralization === true ? undefined : versionModelName,
|
||||
) as unknown as CollectionModel
|
||||
) as CollectionModel
|
||||
// this.payload.versions[collection.slug] = model;
|
||||
this.versions[collection.slug] = model
|
||||
}
|
||||
@@ -57,9 +57,11 @@ export const init: Init = async function init(this: MongooseAdapter) {
|
||||
collection.slug,
|
||||
schema,
|
||||
this.autoPluralization === true ? undefined : collection.slug,
|
||||
) as unknown as CollectionModel
|
||||
) as CollectionModel
|
||||
this.collections[collection.slug] = model
|
||||
|
||||
// TS expect error only needed until we launch 2.0.0
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
this.payload.collections[collection.slug] = {
|
||||
config: collection,
|
||||
}
|
||||
@@ -92,7 +94,7 @@ export const init: Init = async function init(this: MongooseAdapter) {
|
||||
versionModelName,
|
||||
versionSchema,
|
||||
versionModelName,
|
||||
) as unknown as CollectionModel
|
||||
) as CollectionModel
|
||||
this.versions[global.slug] = versionsModel
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { readMigrationFiles } from 'payload/database'
|
||||
import { commitTransaction } from 'payload/dist/utilities/commitTransaction'
|
||||
import { initTransaction } from 'payload/dist/utilities/initTransaction'
|
||||
import { killTransaction } from 'payload/dist/utilities/killTransaction'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { MongooseAdapter } from '.'
|
||||
@@ -8,15 +11,19 @@ import type { MongooseAdapter } from '.'
|
||||
/**
|
||||
* Drop the current database and run all migrate up functions
|
||||
*/
|
||||
export async function migrateFresh(this: MongooseAdapter): Promise<void> {
|
||||
export async function migrateFresh(
|
||||
this: MongooseAdapter,
|
||||
{ forceAcceptWarning = false }: { forceAcceptWarning?: boolean },
|
||||
): Promise<void> {
|
||||
const { payload } = this
|
||||
|
||||
if (!forceAcceptWarning) {
|
||||
const { confirm: acceptWarning } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message: `WARNING: This will drop your database and run all migrations. Are you sure you want to proceed?`,
|
||||
type: 'confirm',
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
@@ -28,6 +35,7 @@ export async function migrateFresh(this: MongooseAdapter): Promise<void> {
|
||||
if (!acceptWarning) {
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
payload.logger.info({
|
||||
msg: `Dropping database.`,
|
||||
@@ -40,29 +48,29 @@ export async function migrateFresh(this: MongooseAdapter): Promise<void> {
|
||||
msg: `Found ${migrationFiles.length} migration files.`,
|
||||
})
|
||||
|
||||
let transactionID
|
||||
const req = { payload } as PayloadRequest
|
||||
|
||||
// Run all migrate up
|
||||
for (const migration of migrationFiles) {
|
||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||
try {
|
||||
const start = Date.now()
|
||||
transactionID = await this.beginTransaction()
|
||||
await migration.up({ payload })
|
||||
await initTransaction(req)
|
||||
await migration.up({ payload, req })
|
||||
await payload.create({
|
||||
collection: 'payload-migrations',
|
||||
data: {
|
||||
name: migration.name,
|
||||
batch: 1,
|
||||
},
|
||||
req: {
|
||||
transactionID,
|
||||
} as PayloadRequest,
|
||||
req,
|
||||
})
|
||||
await this.commitTransaction(transactionID)
|
||||
|
||||
await commitTransaction(req)
|
||||
|
||||
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
|
||||
} catch (err: unknown) {
|
||||
await this.rollbackTransaction(transactionID)
|
||||
await killTransaction(req)
|
||||
payload.logger.error({
|
||||
err,
|
||||
msg: `Error running migration ${migration.name}. Rolling back.`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Field, Payload, Where } from 'payload/types'
|
||||
import type { Payload } from 'payload'
|
||||
import type { Field, Where } from 'payload/types'
|
||||
|
||||
import { QueryError } from 'payload/errors'
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Payload } from 'payload'
|
||||
import type { PathToQuery } from 'payload/database'
|
||||
import type { Field, Payload } from 'payload/types'
|
||||
import type { Field } from 'payload/types'
|
||||
import type { Operator } from 'payload/types'
|
||||
|
||||
import objectID from 'bson-objectid'
|
||||
@@ -15,7 +16,8 @@ import { sanitizeQueryValue } from './sanitizeQueryValue'
|
||||
|
||||
type SearchParam = {
|
||||
path?: string
|
||||
value: unknown
|
||||
rawQuery?: unknown
|
||||
value?: unknown
|
||||
}
|
||||
|
||||
const subQueryOptions = {
|
||||
@@ -91,7 +93,11 @@ export async function buildSearchParam({
|
||||
const [{ field, path }] = paths
|
||||
|
||||
if (path) {
|
||||
const { operator: formattedOperator, val: formattedValue } = sanitizeQueryValue({
|
||||
const {
|
||||
operator: formattedOperator,
|
||||
rawQuery,
|
||||
val: formattedValue,
|
||||
} = sanitizeQueryValue({
|
||||
field,
|
||||
hasCustomID,
|
||||
operator,
|
||||
@@ -99,6 +105,8 @@ export async function buildSearchParam({
|
||||
val,
|
||||
})
|
||||
|
||||
if (rawQuery) return { value: rawQuery }
|
||||
|
||||
// If there are multiple collections to search through,
|
||||
// Recursively build up a list of query constraints
|
||||
if (paths.length > 1) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import type { FilterQuery } from 'mongoose'
|
||||
import type { Operator, Payload, Where } from 'payload/types'
|
||||
import type { Payload } from 'payload'
|
||||
import type { Operator, Where } from 'payload/types'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import deepmerge from 'deepmerge'
|
||||
|
||||
@@ -17,7 +17,11 @@ export const sanitizeQueryValue = ({
|
||||
operator,
|
||||
path,
|
||||
val,
|
||||
}: SanitizeQueryValueArgs): { operator: string; val: unknown } => {
|
||||
}: SanitizeQueryValueArgs): {
|
||||
operator?: string
|
||||
rawQuery?: unknown
|
||||
val?: unknown
|
||||
} => {
|
||||
let formattedValue = val
|
||||
let formattedOperator = operator
|
||||
|
||||
@@ -70,6 +74,24 @@ export const sanitizeQueryValue = ({
|
||||
formattedValue = null
|
||||
}
|
||||
|
||||
// Object equality requires the value to be the first key in the object that is being queried.
|
||||
if (
|
||||
operator === 'equals' &&
|
||||
formattedValue &&
|
||||
typeof formattedValue === 'object' &&
|
||||
formattedValue.value &&
|
||||
formattedValue.relationTo
|
||||
) {
|
||||
return {
|
||||
rawQuery: {
|
||||
$and: [
|
||||
{ [`${path}.value`]: { $eq: formattedValue.value } },
|
||||
{ [`${path}.relationTo`]: { $eq: formattedValue.relationTo } },
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (operator === 'in' && Array.isArray(formattedValue)) {
|
||||
formattedValue = formattedValue.reduce((formattedValues, inVal) => {
|
||||
const newValues = [inVal]
|
||||
@@ -104,7 +126,7 @@ export const sanitizeQueryValue = ({
|
||||
formattedValue = undefined
|
||||
} else {
|
||||
formattedValue = {
|
||||
$geometry: { coordinates: [parseFloat(lng), parseFloat(lat)], type: 'Point' },
|
||||
$geometry: { type: 'Point', coordinates: [parseFloat(lng), parseFloat(lat)] },
|
||||
}
|
||||
|
||||
if (maxDistance) formattedValue.$maxDistance = parseFloat(maxDistance)
|
||||
@@ -135,6 +157,23 @@ export const sanitizeQueryValue = ({
|
||||
|
||||
if (operator === 'exists') {
|
||||
formattedValue = formattedValue === 'true' || formattedValue === true
|
||||
|
||||
// Clearable fields
|
||||
if (['relationship', 'select', 'upload'].includes(field.type)) {
|
||||
if (formattedValue) {
|
||||
return {
|
||||
rawQuery: {
|
||||
$and: [{ [path]: { $exists: true } }, { [path]: { $ne: null } }],
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
rawQuery: {
|
||||
$or: [{ [path]: { $exists: false } }, { [path]: { $eq: null } }],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { operator: formattedOperator, val: formattedValue }
|
||||
|
||||
@@ -58,8 +58,15 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding a hint.
|
||||
if (
|
||||
!useEstimatedCount &&
|
||||
Object.keys(versionQuery).length === 0 &&
|
||||
this.disableIndexHints !== true
|
||||
) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||
// the correct indexed field
|
||||
paginationOptions.useCustomCountFn = () => {
|
||||
return Promise.resolve(
|
||||
VersionModel.countDocuments(versionQuery, {
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
// @ts-expect-error // TODO: Fix this import
|
||||
import type { TransactionOptions } from 'mongodb'
|
||||
import type { BeginTransaction } from 'payload/database'
|
||||
|
||||
import { APIError } from 'payload/errors'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
let transactionsNotAvailable: boolean
|
||||
import type { MongooseAdapter } from '../index'
|
||||
|
||||
export const beginTransaction: BeginTransaction = async function beginTransaction(
|
||||
options: TransactionOptions = {},
|
||||
this: MongooseAdapter,
|
||||
options: TransactionOptions,
|
||||
) {
|
||||
let id = null
|
||||
if (!this.connection) {
|
||||
throw new APIError('beginTransaction called while no connection to the database exists')
|
||||
}
|
||||
|
||||
if (transactionsNotAvailable) return id
|
||||
|
||||
const client = this.connection.getClient()
|
||||
if (!client.options.replicaSet) {
|
||||
transactionsNotAvailable = true
|
||||
} else {
|
||||
id = uuid()
|
||||
const id = uuid()
|
||||
|
||||
if (!this.sessions[id]) {
|
||||
this.sessions[id] = await client.startSession()
|
||||
this.sessions[id] = client.startSession()
|
||||
}
|
||||
if (this.sessions[id].inTransaction()) {
|
||||
this.payload.logger.warn('beginTransaction called while transaction already exists')
|
||||
} else {
|
||||
await this.sessions[id].startTransaction(options)
|
||||
}
|
||||
this.sessions[id].startTransaction(options || (this.transactionOptions as TransactionOptions))
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { UpdateOne } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { ValidationError } from 'payload/errors'
|
||||
|
||||
import type { MongooseAdapter } from '.'
|
||||
|
||||
import handleError from './utilities/handleError'
|
||||
import sanitizeInternalFields from './utilities/sanitizeInternalFields'
|
||||
import { withSession } from './withSession'
|
||||
|
||||
@@ -30,18 +29,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
try {
|
||||
result = await Model.findOneAndUpdate(query, data, options)
|
||||
} catch (error) {
|
||||
// Handle uniqueness error from MongoDB
|
||||
throw error.code === 11000 && error.keyValue
|
||||
? new ValidationError(
|
||||
[
|
||||
{
|
||||
field: Object.keys(error.keyValue)[0],
|
||||
message: 'Value must be unique',
|
||||
},
|
||||
],
|
||||
req.t,
|
||||
)
|
||||
: error
|
||||
handleError(error, req)
|
||||
}
|
||||
|
||||
result = JSON.parse(JSON.stringify(result))
|
||||
|
||||
23
packages/db-mongodb/src/utilities/handleError.ts
Normal file
23
packages/db-mongodb/src/utilities/handleError.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import httpStatus from 'http-status'
|
||||
import { APIError, ValidationError } from 'payload/errors'
|
||||
|
||||
const handleError = (error, req) => {
|
||||
// Handle uniqueness error from MongoDB
|
||||
if (error.code === 11000 && error.keyValue) {
|
||||
throw new ValidationError(
|
||||
[
|
||||
{
|
||||
field: Object.keys(error.keyValue)[0],
|
||||
message: req.t('error:valueMustBeUnique'),
|
||||
},
|
||||
],
|
||||
req.t,
|
||||
)
|
||||
} else if (error.code === 11000) {
|
||||
throw new APIError(req.t('error:valueMustBeUnique'), httpStatus.BAD_REQUEST)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export default handleError
|
||||
650
pnpm-lock.yaml
generated
650
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -35,12 +35,8 @@ export default buildConfigWithDefaults({
|
||||
name: 'adminOnlyField',
|
||||
type: 'text',
|
||||
access: {
|
||||
read: ({
|
||||
req: {
|
||||
user: { roles = [] },
|
||||
},
|
||||
}) => {
|
||||
return roles.includes('admin')
|
||||
read: ({ req: { user } }) => {
|
||||
return user?.roles?.includes('admin')
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -177,7 +173,8 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
if (user.collection === 'api-keys') {
|
||||
if (!user) return false
|
||||
if (user?.collection === 'api-keys') {
|
||||
return {
|
||||
id: {
|
||||
equals: user.id,
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { User } from '../../packages/payload/src/auth'
|
||||
import { getPayload } from '../../packages/payload/src'
|
||||
import { devUser } from '../credentials'
|
||||
import { NextRESTClient } from '../helpers/NextRESTClient'
|
||||
import { startMemoryDB } from '../startMemoryDB'
|
||||
// import { startMemoryDB } from '../startMemoryDB'
|
||||
import configPromise from './config'
|
||||
import { namedSaveToJWTValue, saveToJWTKey, slug } from './shared'
|
||||
|
||||
@@ -18,8 +18,8 @@ const { email, password } = devUser
|
||||
|
||||
describe('Auth', () => {
|
||||
beforeAll(async () => {
|
||||
const config = await startMemoryDB(configPromise)
|
||||
payload = await getPayload({ config })
|
||||
// const config = await startMemoryDB(configPromise)
|
||||
payload = await getPayload({ config: configPromise })
|
||||
restClient = new NextRESTClient(payload.config)
|
||||
})
|
||||
|
||||
@@ -33,24 +33,32 @@ describe('Auth', () => {
|
||||
let token
|
||||
let user
|
||||
beforeAll(async () => {
|
||||
const { data } = await restClient
|
||||
.GRAPHQL_POST({
|
||||
body: JSON.stringify({
|
||||
query: `mutation {
|
||||
loginUser(email: "${devUser.email}", password: "${devUser.password}") {
|
||||
token
|
||||
user {
|
||||
id
|
||||
email
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}),
|
||||
const result = await payload.login({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
|
||||
user = data.loginUser.user
|
||||
token = data.loginUser.token
|
||||
// const { data } = await restClient
|
||||
// .GRAPHQL_POST({
|
||||
// body: JSON.stringify({
|
||||
// query: `mutation {
|
||||
// loginUser(email: "${devUser.email}", password: "${devUser.password}") {
|
||||
// token
|
||||
// user {
|
||||
// id
|
||||
// email
|
||||
// }
|
||||
// }
|
||||
// }`,
|
||||
// }),
|
||||
// })
|
||||
// .then((res) => res.json())
|
||||
|
||||
user = result.user
|
||||
token = result.token
|
||||
})
|
||||
|
||||
it('should login', async () => {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@payloadcms/translations/api": ["./packages/translations/src/all"],
|
||||
"@payloadcms/next/*": ["./packages/next/src/*"],
|
||||
"@payloadcms/graphql": ["./packages/graphql/src"],
|
||||
"payload-config": ["./test/_community/config.ts"]
|
||||
"payload-config": ["./test/auth/config.ts"]
|
||||
}
|
||||
},
|
||||
"exclude": ["dist", "build", "temp", "node_modules"],
|
||||
|
||||
Reference in New Issue
Block a user