Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use custom DB adapters for globals and collections #8193

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a5444d0
chore: wip
kendelljoseph Sep 12, 2024
e2d4a2a
chore: wip
kendelljoseph Sep 12, 2024
7beb4db
chore: updates collection types
kendelljoseph Sep 12, 2024
b693de5
chore: updates globals types
kendelljoseph Sep 12, 2024
25d83c3
chore: adds test for global db
kendelljoseph Sep 17, 2024
f3b8091
chore: adds guard clause to skip init on collections with init defined
kendelljoseph Sep 17, 2024
9b11a75
chore: checks for custom global db before transactions
kendelljoseph Sep 17, 2024
f557504
chore: linting
kendelljoseph Sep 17, 2024
7da131a
chore: correctly retrieves global config
kendelljoseph Sep 17, 2024
d9cbd44
chore: updates docs to include global db operations
kendelljoseph Sep 17, 2024
7baf43a
chore: adds init and connect tests
kendelljoseph Sep 18, 2024
087a30d
chore: adds init and connection tests
kendelljoseph Sep 18, 2024
6b48734
chore: adds test collection logic for init and connect
kendelljoseph Sep 18, 2024
1182870
chore: skips db initialization for collections and globals with their…
kendelljoseph Sep 18, 2024
9a9ff78
chore: skips initialization for collections and globals with their ow…
kendelljoseph Sep 18, 2024
ab7845d
chore: adds connect option to db
kendelljoseph Sep 18, 2024
9f788b1
chore: init and connect to global and collection DBs
kendelljoseph Sep 18, 2024
e202484
chore: updates collections-db test to include skips
kendelljoseph Sep 20, 2024
83a060b
chore: uses strings for id and creates a duplicate collection with db
kendelljoseph Sep 20, 2024
0c4547b
chore: updates docs
kendelljoseph Sep 20, 2024
b691c6c
chore: adds global type
kendelljoseph Sep 20, 2024
358e979
chore: linting variable name
kendelljoseph Sep 20, 2024
f967787
chore: adds generated types for collections-db and globals-db
kendelljoseph Sep 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/database/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ To configure Collection database operations in your Payload application, your Co

The override methods receive arguments useful for augmenting operations such as Field data, the collection slug, and the req.

Relations and Migrations are not supported at this time on collections with custom database operations.

Here is an example:
```ts
import type { CollectionConfig } from 'payload/types'
Expand Down Expand Up @@ -172,3 +174,47 @@ export const Collection: CollectionConfig => {
}

```

## Global Operations

You can also configure database operations on globals.

Like collection operations, the override methods receive arguments useful for augmenting operations such as the global slug, and the req.

Relations and Migrations are not supported at this time on globals with custom database operations.

Here is an example:
```ts
import type { CollectionConfig } from 'payload/types'

export const Collection: CollectionConfig => {
return {
globals: [
{
slug: 'global-db-operations',
db: {
createGlobal: () => {
// ...
},
updateGlobal: () => {
// ...
},
findGlobal: () => {
// ...
},
findGlobalVersions: () => {
// ...
},
},
fields: [
{
name: 'name',
type: 'text',
},
],
},
],
}
}

```
9 changes: 8 additions & 1 deletion packages/db-mongodb/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { PaginateOptions } from 'mongoose'
import type { Init } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import type { SanitizedGlobalConfig } from 'payload/types'

import mongoose from 'mongoose'
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'
Expand All @@ -19,6 +20,9 @@ import { getDBName } from './utilities/getDBName'

export const init: Init = async function init(this: MongooseAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
// Skip collections that have an .init() method
if ('function' === typeof collection?.db?.init) return

const schema = buildCollectionSchema(collection, this)

if (collection.versions) {
Expand Down Expand Up @@ -74,7 +78,10 @@ export const init: Init = async function init(this: MongooseAdapter) {
const model = buildGlobalModel(this)
this.globals = model

this.payload.config.globals.forEach((global) => {
this.payload.config.globals.forEach((global: SanitizedGlobalConfig) => {
// Skip globals that have an .init() method
if ('function' === typeof global?.db?.init) return

if (global.versions) {
const versionModelName = getDBName({ config: global, versions: true })

Expand Down
9 changes: 8 additions & 1 deletion packages/db-postgres/src/init.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable no-param-reassign */
import type { Init } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import type { SanitizedGlobalConfig } from 'payload/types'

import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
Expand All @@ -25,6 +26,9 @@ export const init: Init = async function init(this: PostgresAdapter) {
}

this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
// Skip collections that have an .init() method
if ('function' === typeof collection?.db?.init) return

const tableName = createTableName({
adapter: this,
config: collection,
Expand Down Expand Up @@ -67,7 +71,10 @@ export const init: Init = async function init(this: PostgresAdapter) {
}
})

this.payload.config.globals.forEach((global) => {
this.payload.config.globals.forEach((global: SanitizedGlobalConfig) => {
// Skip globals that have an .init() method
if ('function' === typeof global?.db?.init) return

const tableName = createTableName({ adapter: this, config: global })

buildTable({
Expand Down
20 changes: 15 additions & 5 deletions packages/payload/src/auth/getExecuteStaticAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,21 @@ const getExecuteStaticAccess =
})
}

const doc = await req.payload.db.findOne({
collection: config.slug,
req,
where: queryToBuild,
})
let doc: Record<string, unknown> | undefined

if (config?.db?.findOne) {
doc = await config.db.findOne({
collection: config.slug,
req,
where: queryToBuild,
})
} else {
doc = await req.payload.db.findOne({
collection: config.slug,
req,
where: queryToBuild,
})
}

if (!doc) {
throw new Forbidden(req.t)
Expand Down
11 changes: 9 additions & 2 deletions packages/payload/src/auth/operations/forgotPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,18 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
throw new APIError('Missing email.')
}

let user = await payload.db.findOne<UserDoc>({
const userDbArgs = {
collection: collectionConfig.slug,
req,
where: { email: { equals: data.email.toLowerCase() } },
})
}

let user: UserDoc
if (collectionConfig?.db?.findOne) {
user = await collectionConfig.db.findOne<UserDoc>(userDbArgs)
} else {
user = await payload.db.findOne<UserDoc>(userDbArgs)
}

if (!user) return null

Expand Down
18 changes: 14 additions & 4 deletions packages/payload/src/auth/operations/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@ import type { PayloadRequest } from '../../express/types'
async function init(args: { collection: string; req: PayloadRequest }): Promise<boolean> {
const { collection: slug, req } = args

const doc = await req.payload.db.findOne({
collection: slug,
req,
})
const collectionConfig = req.payload.config.collections[slug]

let doc: Record<string, unknown> | undefined
if (collectionConfig?.db?.findOne) {
doc = await collectionConfig.db.findOne({
collection: slug,
req,
})
} else {
doc = await req.payload.db.findOne({
collection: slug,
req,
})
}

return !!doc
}
Expand Down
11 changes: 9 additions & 2 deletions packages/payload/src/auth/operations/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,18 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(

const email = unsanitizedEmail ? unsanitizedEmail.toLowerCase().trim() : null

let user = await payload.db.findOne<any>({
const userDbArgs = {
collection: collectionConfig.slug,
req,
where: { email: { equals: email.toLowerCase() } },
})
}

let user: any
if (collectionConfig?.db?.findOne) {
user = await collectionConfig.db.findOne(userDbArgs)
} else {
user = await req.payload.db.findOne<any>(userDbArgs)
}

if (!user || (args.collection.config.auth.verify && user._verified === false)) {
throw new AuthenticationError(req.t)
Expand Down
18 changes: 13 additions & 5 deletions packages/payload/src/auth/operations/registerFirstUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections
collection: {
config,
config: {
auth: { verify },
slug,
auth: { verify },
},
},
data,
Expand All @@ -44,10 +44,18 @@ async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections
try {
const shouldCommit = await initTransaction(req)

const doc = await payload.db.findOne({
collection: config.slug,
req,
})
let doc
if (config?.db?.findOne) {
doc = await config.db.findOne({
collection: config.slug,
req,
})
} else {
doc = await payload.db.findOne({
collection: config.slug,
req,
})
}

if (doc) throw new Forbidden(req.t)

Expand Down
21 changes: 17 additions & 4 deletions packages/payload/src/auth/operations/resetPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,21 @@ async function resetPassword(args: Arguments): Promise<Result> {
// Reset Password
// /////////////////////////////////////

const user = await payload.db.findOne<any>({
const userDbArgs = {
collection: collectionConfig.slug,
req,
where: {
resetPasswordExpiration: { greater_than: new Date() },
resetPasswordToken: { equals: data.token },
},
})
}

let user: any
if (collectionConfig?.db?.findOne) {
user = await collectionConfig.db.findOne<any>(userDbArgs)
} else {
user = await payload.db.findOne<any>(userDbArgs)
}

if (!user) throw new APIError('Token is either invalid or has expired.')

Expand All @@ -81,12 +88,18 @@ async function resetPassword(args: Arguments): Promise<Result> {
user._verified = true
}

const doc = await payload.db.updateOne({
const updateDbArgs = {
id: user.id,
collection: collectionConfig.slug,
data: user,
req,
})
}
let doc: any
if (collectionConfig?.db?.updateOne) {
doc = await collectionConfig.db.updateOne(updateDbArgs)
} else {
doc = await payload.db.updateOne(updateDbArgs)
}

await authenticateLocalStrategy({ doc, password: data.password })

Expand Down
11 changes: 9 additions & 2 deletions packages/payload/src/auth/operations/unlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,19 @@ async function unlock(args: Args): Promise<boolean> {
throw new APIError('Missing email.')
}

const user = await req.payload.db.findOne({
const userDbArgs = {
collection: collectionConfig.slug,
locale,
req,
where: { email: { equals: data.email.toLowerCase() } },
})
}

let user: any
if (collectionConfig?.db?.findOne) {
user = await collectionConfig.db.findOne<any>(userDbArgs)
} else {
user = await req.payload.db.findOne<any>(userDbArgs)
}

let result

Expand Down
20 changes: 16 additions & 4 deletions packages/payload/src/auth/operations/verifyEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,25 @@ async function verifyEmail(args: Args): Promise<boolean> {
try {
const shouldCommit = await initTransaction(req)

const user = await req.payload.db.findOne<any>({
const userDbArgs = {
collection: collection.config.slug,
req,
where: {
_verificationToken: { equals: token },
},
})
}
let user: any
if (collection.config?.db?.findOne) {
user = await collection.config.db.findOne<any>(userDbArgs)
} else {
user = await req.payload.db.findOne<any>(userDbArgs)
}

if (!user) throw new APIError('Verification token is invalid.', httpStatus.BAD_REQUEST)
if (user && user._verified === true)
throw new APIError('This account has already been activated.', httpStatus.ACCEPTED)

await req.payload.db.updateOne({
const updateDbArgs = {
id: user.id,
collection: collection.config.slug,
data: {
Expand All @@ -44,7 +50,13 @@ async function verifyEmail(args: Args): Promise<boolean> {
_verified: true,
},
req,
})
}

if (collection.config?.db?.updateOne) {
await collection.config.db.updateOne(updateDbArgs)
} else {
await req.payload.db.updateOne(updateDbArgs)
}

if (shouldCommit) await commitTransaction(req)

Expand Down
29 changes: 25 additions & 4 deletions packages/payload/src/collections/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,10 +387,31 @@ export type CollectionConfig = {
/**
* Add a custom database adapter to this collection.
*/
db?: Pick<
DatabaseAdapter,
'create' | 'deleteMany' | 'deleteOne' | 'find' | 'findOne' | 'updateOne'
>
db?:
| DatabaseAdapter
| Pick<
DatabaseAdapter,
| 'connect'
| 'count'
| 'create'
| 'createGlobal'
| 'createGlobalVersion'
| 'createVersion'
| 'deleteMany'
| 'deleteOne'
| 'deleteVersions'
| 'find'
| 'findGlobal'
| 'findGlobalVersions'
| 'findOne'
| 'findVersions'
| 'init'
| 'queryDrafts'
| 'updateGlobal'
| 'updateGlobalVersion'
| 'updateOne'
| 'updateVersion'
>
/**
* Used to override the default naming of the database table or collection with your using a function or string
* @WARNING: If you change this property with existing data, you will need to handle the renaming of the table in your database or by using migrations
Expand Down
Loading
Loading