feat: add upsert to database interface and adapters (#8397)
- Adds the upsert method to the database interface - Adds a mongodb specific option to extend the updateOne to accept mongoDB Query Options (to pass `upsert: true`) - Added upsert method to all database adapters - Uses db.upsert in the payload preferences update operation Includes a test using payload-preferences
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import type { CollationOptions, TransactionOptions } from 'mongodb'
|
import type { CollationOptions, TransactionOptions } from 'mongodb'
|
||||||
import type { MongoMemoryReplSet } from 'mongodb-memory-server'
|
import type { MongoMemoryReplSet } from 'mongodb-memory-server'
|
||||||
import type { ClientSession, Connection, ConnectOptions } from 'mongoose'
|
import type { ClientSession, Connection, ConnectOptions, QueryOptions } from 'mongoose'
|
||||||
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload } from 'payload'
|
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload, UpdateOneArgs } from 'payload'
|
||||||
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import mongoose from 'mongoose'
|
import mongoose from 'mongoose'
|
||||||
@@ -36,6 +36,7 @@ import { updateGlobal } from './updateGlobal.js'
|
|||||||
import { updateGlobalVersion } from './updateGlobalVersion.js'
|
import { updateGlobalVersion } from './updateGlobalVersion.js'
|
||||||
import { updateOne } from './updateOne.js'
|
import { updateOne } from './updateOne.js'
|
||||||
import { updateVersion } from './updateVersion.js'
|
import { updateVersion } from './updateVersion.js'
|
||||||
|
import { upsert } from './upsert.js'
|
||||||
|
|
||||||
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||||
|
|
||||||
@@ -124,6 +125,7 @@ declare module 'payload' {
|
|||||||
}[]
|
}[]
|
||||||
sessions: Record<number | string, ClientSession>
|
sessions: Record<number | string, ClientSession>
|
||||||
transactionOptions: TransactionOptions
|
transactionOptions: TransactionOptions
|
||||||
|
updateOne: (args: { options?: QueryOptions } & UpdateOneArgs) => Promise<Document>
|
||||||
versions: {
|
versions: {
|
||||||
[slug: string]: CollectionModel
|
[slug: string]: CollectionModel
|
||||||
}
|
}
|
||||||
@@ -191,6 +193,7 @@ export function mongooseAdapter({
|
|||||||
updateGlobalVersion,
|
updateGlobalVersion,
|
||||||
updateOne,
|
updateOne,
|
||||||
updateVersion,
|
updateVersion,
|
||||||
|
upsert,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { QueryOptions } from 'mongoose'
|
||||||
import type { PayloadRequest, UpdateOne } from 'payload'
|
import type { PayloadRequest, UpdateOne } from 'payload'
|
||||||
|
|
||||||
import type { MongooseAdapter } from './index.js'
|
import type { MongooseAdapter } from './index.js'
|
||||||
@@ -9,11 +10,20 @@ import { withSession } from './withSession.js'
|
|||||||
|
|
||||||
export const updateOne: UpdateOne = async function updateOne(
|
export const updateOne: UpdateOne = async function updateOne(
|
||||||
this: MongooseAdapter,
|
this: MongooseAdapter,
|
||||||
{ id, collection, data, locale, req = {} as PayloadRequest, where: whereArg },
|
{
|
||||||
|
id,
|
||||||
|
collection,
|
||||||
|
data,
|
||||||
|
locale,
|
||||||
|
options: optionsArgs = {},
|
||||||
|
req = {} as PayloadRequest,
|
||||||
|
where: whereArg,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const where = id ? { id: { equals: id } } : whereArg
|
const where = id ? { id: { equals: id } } : whereArg
|
||||||
const Model = this.collections[collection]
|
const Model = this.collections[collection]
|
||||||
const options = {
|
const options: QueryOptions = {
|
||||||
|
...optionsArgs,
|
||||||
...(await withSession(this, req)),
|
...(await withSession(this, req)),
|
||||||
lean: true,
|
lean: true,
|
||||||
new: true,
|
new: true,
|
||||||
|
|||||||
10
packages/db-mongodb/src/upsert.ts
Normal file
10
packages/db-mongodb/src/upsert.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { PayloadRequest, Upsert } from 'payload'
|
||||||
|
|
||||||
|
import type { MongooseAdapter } from './index.js'
|
||||||
|
|
||||||
|
export const upsert: Upsert = async function upsert(
|
||||||
|
this: MongooseAdapter,
|
||||||
|
{ collection, data, locale, req = {} as PayloadRequest, where },
|
||||||
|
) {
|
||||||
|
return this.updateOne({ collection, data, locale, options: { upsert: true }, req, where })
|
||||||
|
}
|
||||||
@@ -150,6 +150,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
|||||||
updateGlobalVersion,
|
updateGlobalVersion,
|
||||||
updateOne,
|
updateOne,
|
||||||
updateVersion,
|
updateVersion,
|
||||||
|
upsert: updateOne,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
|||||||
updateGlobalVersion,
|
updateGlobalVersion,
|
||||||
updateOne,
|
updateOne,
|
||||||
updateVersion,
|
updateVersion,
|
||||||
|
upsert: updateOne,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
|
|||||||
updateGlobalVersion,
|
updateGlobalVersion,
|
||||||
updateOne,
|
updateOne,
|
||||||
updateVersion,
|
updateVersion,
|
||||||
|
upsert: updateOne,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ export interface BaseDatabaseAdapter {
|
|||||||
updateOne: UpdateOne
|
updateOne: UpdateOne
|
||||||
|
|
||||||
updateVersion: UpdateVersion
|
updateVersion: UpdateVersion
|
||||||
|
|
||||||
|
upsert: Upsert
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Init = () => Promise<void> | void
|
export type Init = () => Promise<void> | void
|
||||||
@@ -380,6 +382,10 @@ export type UpdateOneArgs = {
|
|||||||
draft?: boolean
|
draft?: boolean
|
||||||
joins?: JoinQuery
|
joins?: JoinQuery
|
||||||
locale?: string
|
locale?: string
|
||||||
|
/**
|
||||||
|
* Additional database adapter specific options to pass to the query
|
||||||
|
*/
|
||||||
|
options?: Record<string, unknown>
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
@@ -394,6 +400,17 @@ export type UpdateOneArgs = {
|
|||||||
|
|
||||||
export type UpdateOne = (args: UpdateOneArgs) => Promise<Document>
|
export type UpdateOne = (args: UpdateOneArgs) => Promise<Document>
|
||||||
|
|
||||||
|
export type UpsertArgs = {
|
||||||
|
collection: string
|
||||||
|
data: Record<string, unknown>
|
||||||
|
joins?: JoinQuery
|
||||||
|
locale?: string
|
||||||
|
req: PayloadRequest
|
||||||
|
where: Where
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Upsert = (args: UpsertArgs) => Promise<Document>
|
||||||
|
|
||||||
export type DeleteOneArgs = {
|
export type DeleteOneArgs = {
|
||||||
collection: string
|
collection: string
|
||||||
joins?: JoinQuery
|
joins?: JoinQuery
|
||||||
|
|||||||
@@ -821,6 +821,7 @@ export type {
|
|||||||
UpdateOneArgs,
|
UpdateOneArgs,
|
||||||
UpdateVersion,
|
UpdateVersion,
|
||||||
UpdateVersionArgs,
|
UpdateVersionArgs,
|
||||||
|
Upsert,
|
||||||
} from './database/types.js'
|
} from './database/types.js'
|
||||||
export type { EmailAdapter as PayloadEmailAdapter, SendEmailOptions } from './email/types.js'
|
export type { EmailAdapter as PayloadEmailAdapter, SendEmailOptions } from './email/types.js'
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -35,23 +35,10 @@ export async function update(args: PreferenceUpdateRequest) {
|
|||||||
value,
|
value,
|
||||||
}
|
}
|
||||||
|
|
||||||
let result
|
return await payload.db.upsert({
|
||||||
|
|
||||||
try {
|
|
||||||
// try/catch because we attempt to update without first reading to check if it exists first to save on db calls
|
|
||||||
result = await payload.db.updateOne({
|
|
||||||
collection,
|
collection,
|
||||||
data: preference,
|
data: preference,
|
||||||
req,
|
req,
|
||||||
where,
|
where,
|
||||||
})
|
})
|
||||||
} catch (err: unknown) {
|
|
||||||
result = await payload.db.create({
|
|
||||||
collection,
|
|
||||||
data: preference,
|
|
||||||
req,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ describe('Auth', () => {
|
|||||||
|
|
||||||
describe('logged in', () => {
|
describe('logged in', () => {
|
||||||
let token: string | undefined
|
let token: string | undefined
|
||||||
let loggedInUser: User | undefined
|
let loggedInUser: undefined | User
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const response = await restClient.POST(`/${slug}/login`, {
|
const response = await restClient.POST(`/${slug}/login`, {
|
||||||
@@ -396,6 +396,52 @@ describe('Auth', () => {
|
|||||||
expect(result.docs).toHaveLength(1)
|
expect(result.docs).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should only have one preference per user per key', async () => {
|
||||||
|
await restClient.POST(`/payload-preferences/${key}`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
value: { property: 'test', property2: 'test' },
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: `JWT ${token}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await restClient.POST(`/payload-preferences/${key}`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
value: { property: 'updated', property2: 'updated' },
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: `JWT ${token}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await payload.find({
|
||||||
|
collection: 'payload-preferences',
|
||||||
|
depth: 0,
|
||||||
|
where: {
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
key: { equals: key },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'user.relationTo': {
|
||||||
|
equals: 'users',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'user.value': {
|
||||||
|
equals: loggedInUser.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.docs[0].value.property).toStrictEqual('updated')
|
||||||
|
expect(result.docs[0].value.property2).toStrictEqual('updated')
|
||||||
|
|
||||||
|
expect(result.docs).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
it('should delete', async () => {
|
it('should delete', async () => {
|
||||||
const response = await restClient.DELETE(`/payload-preferences/${key}`, {
|
const response = await restClient.DELETE(`/payload-preferences/${key}`, {
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
Reference in New Issue
Block a user