chore: pass config to route handlers

This commit is contained in:
Jarrod Flesch
2024-02-13 23:30:02 -05:00
parent da9ca18fe3
commit 018755516b
46 changed files with 536 additions and 617 deletions

View File

@@ -1,4 +1,9 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from 'payload-config'
import { REST_GET, REST_DELETE, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
export { GET, POST, DELETE, PATCH } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)

View File

@@ -1,4 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from 'payload-config'
import { GET_STATIC_FILE } from '@payloadcms/next/routes'
export { GET } from '@payloadcms/next/routes/[collection]/file/[filename]/route'
export const GET = GET_STATIC_FILE(config)

View File

@@ -1,4 +1,5 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export { GET_GraphQLPlayground as GET } from '@payloadcms/next/routes/graphql'
export const GET = GRAPHQL_PLAYGROUND_GET

View File

@@ -1,4 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from 'payload-config'
import { GRAPHQL_POST } from '@payloadcms/next/routes'
export { POST_GraphQLHandler as POST } from '@payloadcms/next/routes/graphql'
export const POST = GRAPHQL_POST(config)

View File

@@ -35,10 +35,6 @@
"./routes": {
"import": "./src/routes/index.ts",
"require": "./src/routes/index.ts"
},
"./routes/*": {
"import": "./src/routes/*.ts",
"require": "./src/routes/*.ts"
}
},
"devDependencies": {
@@ -95,11 +91,6 @@
"import": "./dist/routes/index.js",
"require": "./dist/routes/index.js",
"types": "./dist/routes/index.d.ts"
},
"./routes/*": {
"import": "./dist/routes/*.js",
"require": "./dist/routes/*.js",
"types": "./dist/routes/*.d.ts"
}
},
"registry": "https://registry.npmjs.org/"

View File

@@ -1,4 +1,9 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from 'payload-config'
import { REST_GET, REST_DELETE, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
export { GET, POST, DELETE, PATCH } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)

View File

@@ -1,4 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from 'payload-config'
import { GET_STATIC_FILE } from '@payloadcms/next/routes'
export { GET } from '@payloadcms/next/routes/[collection]/file/[filename]/route'
export const GET = GET_STATIC_FILE(config)

View File

@@ -1,4 +1,5 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export { GET_GraphQLPlayground as GET } from '@payloadcms/next/routes/graphql'
export const GET = GRAPHQL_PLAYGROUND_GET

View File

@@ -1,4 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from 'payload-config'
import { GRAPHQL_POST } from '@payloadcms/next/routes'
export { POST_GraphQLHandler as POST } from '@payloadcms/next/routes/graphql'
export const POST = GRAPHQL_POST(config)

View File

@@ -1,119 +0,0 @@
import path from 'path'
import config from 'payload-config'
import { streamFile } from '../../../../next-stream-file'
import fsPromises from 'fs/promises'
import { Collection, PayloadRequest, Where } from 'payload/types'
import executeAccess from 'payload/dist/auth/executeAccess'
import { APIError, Forbidden } from 'payload/errors'
import { RouteError } from '../../../RouteError'
import { createPayloadRequest } from '../../../../utilities/createPayloadRequest'
import httpStatus from 'http-status'
async function checkFileAccess({
req,
filename,
collection,
}: {
req: PayloadRequest
filename: string
collection: Collection
}) {
const { config } = collection
const accessResult = await executeAccess({ isReadingStaticFile: true, req }, config.access.read)
if (typeof accessResult === 'object') {
const queryToBuild: Where = {
and: [
{
or: [
{
filename: {
equals: filename,
},
},
],
},
accessResult,
],
}
if (config.upload.imageSizes) {
config.upload.imageSizes.forEach(({ name }) => {
queryToBuild.and[0].or.push({
[`sizes.${name}.filename`]: {
equals: filename,
},
})
})
}
const doc = await req.payload.db.findOne({
collection: config.slug,
req,
where: queryToBuild,
})
if (!doc) {
throw new Forbidden(req.t)
}
}
}
export const GET = async (
request: Request,
{ params }: { params: { collection: string; filename: string } },
) => {
const { collection: collectionSlug, filename } = params
let req: PayloadRequest
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: { collection: collectionSlug },
})
collection = req.payload.collections?.[collectionSlug]
if (!collection) {
throw new APIError(`Media collection not found: ${collectionSlug}`, httpStatus.BAD_REQUEST)
}
if (!collection.config.upload) {
throw new APIError(
`This collection is not an upload collection: ${collectionSlug}`,
httpStatus.BAD_REQUEST,
)
}
if (collection.config.upload.disableLocalStorage) {
throw new APIError(
`This collection has local storage disabled: ${collectionSlug}`,
httpStatus.BAD_REQUEST,
)
}
await checkFileAccess({
req,
filename,
collection,
})
const fileDir = collection.config.upload?.staticDir || collection.config.slug
const filePath = path.resolve(`${fileDir}/${filename}`)
const stats = await fsPromises.stat(filePath)
const data = streamFile(filePath)
return new Response(data, {
status: httpStatus.OK,
headers: new Headers({
'content-length': stats.size + '',
}),
})
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}

View File

@@ -1,8 +1,7 @@
import config from 'payload-config'
import { GraphQLError } from 'graphql'
import httpStatus from 'http-status'
import { configToSchema } from '@payloadcms/graphql'
import type { Payload, CollectionAfterErrorHook } from 'payload/types'
import type { Payload, CollectionAfterErrorHook, SanitizedConfig } from 'payload/types'
import type { GraphQLFormattedError } from 'graphql'
import { createPayloadRequest } from '../../utilities/createPayloadRequest'
import { createHandler } from 'graphql-http/lib/use/fetch'
@@ -49,7 +48,7 @@ if (!cached) {
cached = global._payload_graphql = { graphql: null, promise: null }
}
export const getGraphql = async () => {
export const getGraphql = async (config: Promise<SanitizedConfig>) => {
if (cached.graphql) {
return cached.graphql
}
@@ -72,13 +71,13 @@ export const getGraphql = async () => {
return cached.graphql
}
export const POST = async (request: Request) => {
export const POST = (config: Promise<SanitizedConfig>) => async (request: Request) => {
const originalRequest = request.clone()
const req = await createPayloadRequest({
request,
config,
})
const { schema, validationRules } = await getGraphql()
const { schema, validationRules } = await getGraphql(config)
const { payload } = req

View File

@@ -1,2 +1,3 @@
export { POST as POST_GraphQLHandler } from './handler'
export { GET as GET_GraphQLPlayground } from './playground'
export { POST as GRAPHQL_POST } from './handler'
export { GET as GRAPHQL_PLAYGROUND_GET } from './playground'

View File

@@ -1,8 +1,8 @@
import config from 'payload-config'
import { renderPlaygroundPage } from 'graphql-playground-html'
import { createPayloadRequest } from '../../utilities/createPayloadRequest'
import type { SanitizedConfig } from 'payload/types'
export const GET = async (request: Request) => {
export const GET = (config: Promise<SanitizedConfig>) => async (request: Request) => {
const req = await createPayloadRequest({
request,
config,

View File

@@ -1,459 +1,10 @@
import config from 'payload-config'
import httpStatus from 'http-status'
import type { Collection, GlobalConfig, PayloadRequest } from 'payload/types'
import type { Endpoint } from 'payload/config'
import { match } from 'path-to-regexp'
export {
GET as REST_GET,
POST as REST_POST,
DELETE as REST_DELETE,
PATCH as REST_PATCH,
} from './rest'
import { createPayloadRequest } from '../utilities/createPayloadRequest'
import {
CollectionRouteHandler,
CollectionRouteHandlerWithID,
GlobalRouteHandler,
GlobalRouteHandlerWithID,
} from './types'
export { GET as GET_STATIC_FILE } from './rest/[collection]/file/[filename]/route'
import { me } from './auth/me'
import { init } from './auth/init'
import { login } from './auth/login'
import { unlock } from './auth/unlock'
import { access } from './auth/access'
import { logout } from './auth/logout'
import { refresh } from './auth/refresh'
import { find } from './collections/find'
import { create } from './collections/create'
import { update } from './collections/update'
import { deleteDoc } from './collections/delete'
import { verifyEmail } from './auth/verifyEmail'
import { findByID } from './collections/findByID'
import { docAccess } from './collections/docAccess'
import { resetPassword } from './auth/resetPassword'
import { updateByID } from './collections/updateByID'
import { deleteByID } from './collections/deleteByID'
import { forgotPassword } from './auth/forgotPassword'
import { findVersions } from './collections/findVersions'
import { registerFirstUser } from './auth/registerFirstUser'
import { restoreVersion } from './collections/restoreVersion'
import { findVersionByID } from './collections/findVersionByID'
import { findOne } from './globals/findOne'
import { update as updateGlobal } from './globals/update'
import { docAccess as docAccessGlobal } from './globals/docAccess'
import { findVersions as findVersionsGlobal } from './globals/findVersions'
import { restoreVersion as restoreVersionGlobal } from './globals/restoreVersion'
import { findVersionByID as findVersionByIdGlobal } from './globals/findVersionByID'
import { RouteError } from './RouteError'
const endpoints = {
root: {
GET: {
access,
},
},
collection: {
GET: {
init,
me,
versions: findVersions,
find,
findByID,
'doc-access-by-id': docAccess,
'doc-versions-by-id': findVersionByID,
},
POST: {
create,
login,
logout,
unlock,
access: docAccess,
'first-register': registerFirstUser,
'forgot-password': forgotPassword,
'reset-password': resetPassword,
'refresh-token': refresh,
'doc-access-by-id': docAccess,
'doc-versions-by-id': restoreVersion,
'doc-verify-by-id': verifyEmail,
},
PATCH: {
update,
updateByID,
},
DELETE: {
delete: deleteDoc,
deleteByID,
},
},
global: {
GET: {
findOne,
'doc-access': docAccessGlobal,
'doc-versions': findVersionsGlobal,
'doc-versions-by-id': findVersionByIdGlobal,
},
POST: {
update: updateGlobal,
'doc-access': docAccessGlobal,
'doc-versions-by-id': restoreVersionGlobal,
},
},
}
const handleCustomEndpoints = ({
entitySlug,
endpoints,
payloadRequest,
}: {
entitySlug?: string
endpoints: Endpoint[] | GlobalConfig['endpoints']
payloadRequest: PayloadRequest
}): Promise<Response> | Response => {
if (endpoints && endpoints.length > 0) {
let handlerParams = {}
const { pathname } = payloadRequest
const pathPrefix =
payloadRequest.payload.config.routes.api + (entitySlug ? `/${entitySlug}` : '')
const customEndpoint = endpoints.find((endpoint) => {
if (endpoint.method === payloadRequest.method.toLowerCase()) {
const pathMatchFn = match(`${pathPrefix}${endpoint.path}`, {
decode: decodeURIComponent,
})
const tempParams = pathMatchFn(pathname)
if (tempParams) {
handlerParams = tempParams.params
return true
}
}
})
if (customEndpoint) {
return customEndpoint.handler({
req: payloadRequest,
routeParams: handlerParams,
})
}
}
return null
}
const RouteNotFoundResponse = (slug: string[]) =>
Response.json(
{
message: `Route Not Found: "${slug.join('/')}"`,
},
{ status: httpStatus.NOT_FOUND },
)
export const GET = async (
request: Request,
{ params: { slug } }: { params: { slug: string[] } },
) => {
const [slug1, slug2, slug3, slug4] = slug
let req: PayloadRequest
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
collection = req.payload.collections?.[slug1]
if (collection) {
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: slug1,
payloadRequest: req,
endpoints: collection.config?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 1:
// /:collection
return endpoints.collection.GET.find({ req, collection })
case 2:
if (slug2 in endpoints.collection.GET) {
// /:collection/init
// /:collection/me
// /:collection/versions
return (endpoints.collection.GET[slug2] as CollectionRouteHandler)({ req, collection })
}
// /:collection/:id
return endpoints.collection.GET.findByID({ req, id: slug2, collection })
case 3:
if (`doc-${slug2}-by-id` in endpoints.collection.GET) {
// /:collection/access/:id
// /:collection/versions/:id
return (endpoints.collection.GET[`doc-${slug2}-by-id`] as CollectionRouteHandlerWithID)(
{ req, id: slug3, collection },
)
}
break
}
} else if (slug1 === 'globals') {
const globalConfig = req.payload.config.globals.find((global) => global.slug === slug2)
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: `${slug1}/${slug2}`,
payloadRequest: req,
endpoints: globalConfig?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 2:
// /globals/:slug
return endpoints.global.GET.findOne({ req, globalConfig })
case 3:
if (`doc-${slug3}` in endpoints.global.GET) {
// /globals/:slug/access
// /globals/:slug/versions
return (endpoints.global.GET?.[`doc-${slug3}`] as GlobalRouteHandler)({
req,
globalConfig,
})
}
break
case 4:
if (`doc-${slug3}-by-id` in endpoints.global.GET) {
// /globals/:slug/versions/:id
return (endpoints.global.GET?.[`doc-${slug3}-by-id`] as GlobalRouteHandlerWithID)({
req,
id: slug4,
globalConfig,
})
}
break
}
} else if (slug.length === 1 && slug1 === 'access') {
return endpoints.root.GET.access({ req })
}
// root routes
const customEndpointResponse = await handleCustomEndpoints({
payloadRequest: req,
endpoints: req.payload.config.endpoints,
})
if (customEndpointResponse) return customEndpointResponse
return RouteNotFoundResponse(slug)
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}
export const POST = async (
request: Request,
{ params: { slug } }: { params: { slug: string[] } },
) => {
const [slug1, slug2, slug3, slug4] = slug
let req: PayloadRequest
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: { collection: slug1 },
})
collection = req.payload.collections?.[slug1]
if (collection) {
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: slug1,
payloadRequest: req,
endpoints: collection.config?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 1:
// /:collection
return endpoints.collection.POST.create({ req, collection })
case 2:
if (slug2 in endpoints.collection.POST) {
// /:collection/login
// /:collection/logout
// /:collection/unlock
// /:collection/access
// /:collection/first-register
// /:collection/forgot-password
// /:collection/reset-password
// /:collection/refresh-token
return (endpoints.collection.POST?.[slug2] as CollectionRouteHandler)({
req,
collection,
})
}
break
case 3:
if (`doc-${slug2}-by-id` in endpoints.collection.POST) {
// /:collection/access/:id
// /:collection/versions/:id
// /:collection/verify/:token ("doc-verify-by-id" uses id as token internally)
return (
endpoints.collection.POST[`doc-${slug2}-by-id`] as CollectionRouteHandlerWithID
)({ req, id: slug3, collection })
}
break
}
} else if (slug1 === 'globals' && slug2) {
const globalConfig = req.payload.config.globals.find((global) => global.slug === slug2)
switch (slug.length) {
case 2:
// /globals/:slug
return endpoints.global.POST.update({ req, globalConfig })
case 3:
if (`doc-${slug3}` in endpoints.global.POST) {
// /globals/:slug/access
return (endpoints.global.POST?.[`doc-${slug3}`] as GlobalRouteHandler)({
req,
globalConfig,
})
}
break
case 4:
if (`doc-${slug3}-by-id` in endpoints.global.POST) {
// /globals/:slug/versions/:id
return (endpoints.global.POST?.[`doc-${slug3}-by-id`] as GlobalRouteHandlerWithID)({
req,
id: slug4,
globalConfig,
})
}
break
default:
return new Response('Route Not Found', { status: 404 })
}
}
// root routes
const customEndpointResponse = await handleCustomEndpoints({
payloadRequest: req,
endpoints: req.payload.config.endpoints,
})
if (customEndpointResponse) return customEndpointResponse
return RouteNotFoundResponse(slug)
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}
export const DELETE = async (
request: Request,
{ params: { slug } }: { params: { slug: string[] } },
) => {
const [slug1, slug2] = slug
let req: PayloadRequest
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
collection = req.payload.collections?.[slug1]
if (collection) {
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: slug1,
payloadRequest: req,
endpoints: collection.config?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 1:
// /:collection
return endpoints.collection.DELETE.delete({ req, collection })
case 2:
// /:collection/:id
return endpoints.collection.DELETE.deleteByID({ req, id: slug2, collection })
}
}
// root routes
const customEndpointResponse = await handleCustomEndpoints({
payloadRequest: req,
endpoints: req.payload.config.endpoints,
})
if (customEndpointResponse) return customEndpointResponse
return RouteNotFoundResponse(slug)
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}
export const PATCH = async (
request: Request,
{ params: { slug } }: { params: { slug: string[] } },
) => {
const [slug1, slug2] = slug
let req: PayloadRequest
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
collection = req.payload.collections?.[slug1]
if (collection) {
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: slug1,
payloadRequest: req,
endpoints: collection.config?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 1:
// /:collection
return endpoints.collection.PATCH.update({ req, collection })
case 2:
// /:collection/:id
return endpoints.collection.PATCH.updateByID({ req, id: slug2, collection })
}
}
// root routes
const customEndpointResponse = await handleCustomEndpoints({
payloadRequest: req,
endpoints: req.payload.config.endpoints,
})
if (customEndpointResponse) return customEndpointResponse
return RouteNotFoundResponse(slug)
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}
export { GRAPHQL_POST, GRAPHQL_PLAYGROUND_GET } from './graphql'

View File

@@ -1,12 +1,11 @@
import path from 'path'
import config from 'payload-config'
import { streamFile } from '../../../next-stream-file'
import { streamFile } from '../../../../../next-stream-file'
import fsPromises from 'fs/promises'
import { Collection, PayloadRequest, Where } from 'payload/types'
import type { Collection, PayloadRequest, SanitizedConfig, Where } from 'payload/types'
import executeAccess from 'payload/dist/auth/executeAccess'
import { APIError, Forbidden } from 'payload/errors'
import { RouteError } from '../../RouteError'
import { createPayloadRequest } from '../../../utilities/createPayloadRequest'
import { RouteError } from '../../../RouteError'
import { createPayloadRequest } from '../../../../../utilities/createPayloadRequest'
import httpStatus from 'http-status'
async function checkFileAccess({
@@ -60,8 +59,9 @@ async function checkFileAccess({
}
export const GET =
(collectionSlug: string) =>
async (request: Request, { params }: { params: { filename: string } }) => {
(config: Promise<SanitizedConfig>) =>
async (request: Request, { params }: { params: { collection: string; filename: string } }) => {
const { collection: collectionSlug, filename } = params
let req: PayloadRequest
let collection: Collection
@@ -91,14 +91,6 @@ export const GET =
)
}
const { filename } = params
if (!filename) {
throw new APIError(
'No filename provided, ensure this route is within a [filename] folder, i.e. staticDir/[filename]/route.ts',
httpStatus.BAD_REQUEST,
)
}
await checkFileAccess({
req,
filename,

View File

@@ -0,0 +1,484 @@
import httpStatus from 'http-status'
import type { Collection, GlobalConfig, PayloadRequest, SanitizedConfig } from 'payload/types'
import type { Endpoint } from 'payload/config'
import { match } from 'path-to-regexp'
import { createPayloadRequest } from '../../utilities/createPayloadRequest'
import {
CollectionRouteHandler,
CollectionRouteHandlerWithID,
GlobalRouteHandler,
GlobalRouteHandlerWithID,
} from './types'
import { me } from './auth/me'
import { init } from './auth/init'
import { login } from './auth/login'
import { unlock } from './auth/unlock'
import { access } from './auth/access'
import { logout } from './auth/logout'
import { refresh } from './auth/refresh'
import { find } from './collections/find'
import { create } from './collections/create'
import { update } from './collections/update'
import { deleteDoc } from './collections/delete'
import { verifyEmail } from './auth/verifyEmail'
import { findByID } from './collections/findByID'
import { docAccess } from './collections/docAccess'
import { resetPassword } from './auth/resetPassword'
import { updateByID } from './collections/updateByID'
import { deleteByID } from './collections/deleteByID'
import { forgotPassword } from './auth/forgotPassword'
import { findVersions } from './collections/findVersions'
import { registerFirstUser } from './auth/registerFirstUser'
import { restoreVersion } from './collections/restoreVersion'
import { findVersionByID } from './collections/findVersionByID'
import { findOne } from './globals/findOne'
import { update as updateGlobal } from './globals/update'
import { docAccess as docAccessGlobal } from './globals/docAccess'
import { findVersions as findVersionsGlobal } from './globals/findVersions'
import { restoreVersion as restoreVersionGlobal } from './globals/restoreVersion'
import { findVersionByID as findVersionByIdGlobal } from './globals/findVersionByID'
import { RouteError } from './RouteError'
const endpoints = {
root: {
GET: {
access,
},
},
collection: {
GET: {
init,
me,
versions: findVersions,
find,
findByID,
'doc-access-by-id': docAccess,
'doc-versions-by-id': findVersionByID,
},
POST: {
create,
login,
logout,
unlock,
access: docAccess,
'first-register': registerFirstUser,
'forgot-password': forgotPassword,
'reset-password': resetPassword,
'refresh-token': refresh,
'doc-access-by-id': docAccess,
'doc-versions-by-id': restoreVersion,
'doc-verify-by-id': verifyEmail,
},
PATCH: {
update,
updateByID,
},
DELETE: {
delete: deleteDoc,
deleteByID,
},
},
global: {
GET: {
findOne,
'doc-access': docAccessGlobal,
'doc-versions': findVersionsGlobal,
'doc-versions-by-id': findVersionByIdGlobal,
},
POST: {
update: updateGlobal,
'doc-access': docAccessGlobal,
'doc-versions-by-id': restoreVersionGlobal,
},
},
}
const handleCustomEndpoints = ({
entitySlug,
endpoints,
payloadRequest,
}: {
entitySlug?: string
endpoints: Endpoint[] | GlobalConfig['endpoints']
payloadRequest: PayloadRequest
}): Promise<Response> | Response => {
if (endpoints && endpoints.length > 0) {
let handlerParams = {}
const { pathname } = payloadRequest
const pathPrefix =
payloadRequest.payload.config.routes.api + (entitySlug ? `/${entitySlug}` : '')
const customEndpoint = endpoints.find((endpoint) => {
if (endpoint.method === payloadRequest.method.toLowerCase()) {
const pathMatchFn = match(`${pathPrefix}${endpoint.path}`, {
decode: decodeURIComponent,
})
const tempParams = pathMatchFn(pathname)
if (tempParams) {
handlerParams = tempParams.params
return true
}
}
})
if (customEndpoint) {
return customEndpoint.handler({
req: payloadRequest,
routeParams: handlerParams,
})
}
}
return null
}
const RouteNotFoundResponse = (slug: string[]) =>
Response.json(
{
message: `Route Not Found: "${slug.join('/')}"`,
},
{ status: httpStatus.NOT_FOUND },
)
export const GET =
(config: Promise<SanitizedConfig>) =>
async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => {
const [slug1, slug2, slug3, slug4] = slug
let req: PayloadRequest
let res: Response
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
collection = req.payload.collections?.[slug1]
if (collection) {
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: slug1,
payloadRequest: req,
endpoints: collection.config?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 1:
// /:collection
res = await endpoints.collection.GET.find({ req, collection })
break
case 2:
if (slug2 in endpoints.collection.GET) {
// /:collection/init
// /:collection/me
// /:collection/versions
res = await (endpoints.collection.GET[slug2] as CollectionRouteHandler)({
req,
collection,
})
} else {
// /:collection/:id
res = await endpoints.collection.GET.findByID({ req, id: slug2, collection })
}
break
case 3:
if (`doc-${slug2}-by-id` in endpoints.collection.GET) {
// /:collection/access/:id
// /:collection/versions/:id
res = await (
endpoints.collection.GET[`doc-${slug2}-by-id`] as CollectionRouteHandlerWithID
)({ req, id: slug3, collection })
}
break
}
} else if (slug1 === 'globals') {
const globalConfig = req.payload.config.globals.find((global) => global.slug === slug2)
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: `${slug1}/${slug2}`,
payloadRequest: req,
endpoints: globalConfig?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 2:
// /globals/:slug
res = await endpoints.global.GET.findOne({ req, globalConfig })
break
case 3:
if (`doc-${slug3}` in endpoints.global.GET) {
// /globals/:slug/access
// /globals/:slug/versions
res = await (endpoints.global.GET?.[`doc-${slug3}`] as GlobalRouteHandler)({
req,
globalConfig,
})
}
break
case 4:
if (`doc-${slug3}-by-id` in endpoints.global.GET) {
// /globals/:slug/versions/:id
res = await (
endpoints.global.GET?.[`doc-${slug3}-by-id`] as GlobalRouteHandlerWithID
)({
req,
id: slug4,
globalConfig,
})
}
break
}
} else if (slug.length === 1 && slug1 === 'access') {
res = await endpoints.root.GET.access({ req })
}
if (res instanceof Response) return res
// root routes
const customEndpointResponse = await handleCustomEndpoints({
payloadRequest: req,
endpoints: req.payload.config.endpoints,
})
if (customEndpointResponse) return customEndpointResponse
return RouteNotFoundResponse(slug)
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}
export const POST =
(config: Promise<SanitizedConfig>) =>
async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => {
const [slug1, slug2, slug3, slug4] = slug
let req: PayloadRequest
let res: Response
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: { collection: slug1 },
})
collection = req.payload.collections?.[slug1]
if (collection) {
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: slug1,
payloadRequest: req,
endpoints: collection.config?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 1:
// /:collection
res = await endpoints.collection.POST.create({ req, collection })
break
case 2:
if (slug2 in endpoints.collection.POST) {
// /:collection/login
// /:collection/logout
// /:collection/unlock
// /:collection/access
// /:collection/first-register
// /:collection/forgot-password
// /:collection/reset-password
// /:collection/refresh-token
res = await (endpoints.collection.POST?.[slug2] as CollectionRouteHandler)({
req,
collection,
})
}
break
case 3:
if (`doc-${slug2}-by-id` in endpoints.collection.POST) {
// /:collection/access/:id
// /:collection/versions/:id
// /:collection/verify/:token ("doc-verify-by-id" uses id as token internally)
res = await (
endpoints.collection.POST[`doc-${slug2}-by-id`] as CollectionRouteHandlerWithID
)({ req, id: slug3, collection })
}
break
}
} else if (slug1 === 'globals' && slug2) {
const globalConfig = req.payload.config.globals.find((global) => global.slug === slug2)
switch (slug.length) {
case 2:
// /globals/:slug
res = await endpoints.global.POST.update({ req, globalConfig })
break
case 3:
if (`doc-${slug3}` in endpoints.global.POST) {
// /globals/:slug/access
res = await (endpoints.global.POST?.[`doc-${slug3}`] as GlobalRouteHandler)({
req,
globalConfig,
})
}
break
case 4:
if (`doc-${slug3}-by-id` in endpoints.global.POST) {
// /globals/:slug/versions/:id
res = await (
endpoints.global.POST?.[`doc-${slug3}-by-id`] as GlobalRouteHandlerWithID
)({
req,
id: slug4,
globalConfig,
})
}
break
default:
res = new Response('Route Not Found', { status: 404 })
}
}
if (res instanceof Response) return res
// root routes
const customEndpointResponse = await handleCustomEndpoints({
payloadRequest: req,
endpoints: req.payload.config.endpoints,
})
if (customEndpointResponse) return customEndpointResponse
return RouteNotFoundResponse(slug)
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}
export const DELETE =
(config: Promise<SanitizedConfig>) =>
async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => {
const [slug1, slug2] = slug
let req: PayloadRequest
let res: Response
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
collection = req.payload.collections?.[slug1]
if (collection) {
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: slug1,
payloadRequest: req,
endpoints: collection.config?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 1:
// /:collection
res = await endpoints.collection.DELETE.delete({ req, collection })
break
case 2:
// /:collection/:id
res = await endpoints.collection.DELETE.deleteByID({ req, id: slug2, collection })
break
}
}
if (res instanceof Response) return res
// root routes
const customEndpointResponse = await handleCustomEndpoints({
payloadRequest: req,
endpoints: req.payload.config.endpoints,
})
if (customEndpointResponse) return customEndpointResponse
return RouteNotFoundResponse(slug)
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}
export const PATCH =
(config: Promise<SanitizedConfig>) =>
async (request: Request, { params: { slug } }: { params: { slug: string[] } }) => {
const [slug1, slug2] = slug
let req: PayloadRequest
let res: Response
let collection: Collection
try {
req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
collection = req.payload.collections?.[slug1]
if (collection) {
const customEndpointResponse = await handleCustomEndpoints({
entitySlug: slug1,
payloadRequest: req,
endpoints: collection.config?.endpoints || [],
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
case 1:
// /:collection
res = await endpoints.collection.PATCH.update({ req, collection })
break
case 2:
// /:collection/:id
res = await endpoints.collection.PATCH.updateByID({ req, id: slug2, collection })
break
}
}
if (res instanceof Response) return res
// root routes
const customEndpointResponse = await handleCustomEndpoints({
payloadRequest: req,
endpoints: req.payload.config.endpoints,
})
if (customEndpointResponse) return customEndpointResponse
return RouteNotFoundResponse(slug)
} catch (error) {
return RouteError({
req,
collection,
err: error,
})
}
}