diff --git a/app/(payload)/api/[collection]/file/[filename]/route.ts b/app/(payload)/api/[collection]/file/[filename]/route.ts deleted file mode 100644 index 82c06625d..000000000 --- a/app/(payload)/api/[collection]/file/[filename]/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* 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/index.js' - -export const GET = GET_STATIC_FILE(config) diff --git a/packages/next/src/routes/index.ts b/packages/next/src/routes/index.ts index c2d8ac679..2731fa77a 100644 --- a/packages/next/src/routes/index.ts +++ b/packages/next/src/routes/index.ts @@ -6,5 +6,3 @@ export { PATCH as REST_PATCH, POST as REST_POST, } from './rest/index.js' - -export { GET as GET_STATIC_FILE } from './rest/[collection]/file/[filename]/route.js' diff --git a/packages/next/src/routes/rest/[collection]/file/[filename]/route.ts b/packages/next/src/routes/rest/[collection]/file/[filename]/route.ts deleted file mode 100644 index fed3950f6..000000000 --- a/packages/next/src/routes/rest/[collection]/file/[filename]/route.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { Collection, PayloadRequest, SanitizedConfig, Where } from 'payload/types' - -import fsPromises from 'fs/promises' -import httpStatus from 'http-status' -import path from 'path' -import { executeAccess } from 'payload/auth' -import { APIError, Forbidden } from 'payload/errors' - -import { streamFile } from '../../../../../next-stream-file/index.js' -import { createPayloadRequest } from '../../../../../utilities/createPayloadRequest.js' -import { RouteError } from '../../../RouteError.js' -import { endpointsAreDisabled } from '../../../checkEndpoints.js' - -async function checkFileAccess({ - collection, - filename, - req, -}: { - collection: Collection - filename: string - req: PayloadRequest -}) { - const { config } = collection - const disableEndpoints = endpointsAreDisabled({ endpoints: config.endpoints, request: req }) - if (disableEndpoints) return disableEndpoints - - 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 = - (config: Promise) => - async (request: Request, { params }: { params: { collection: string; filename: string } }) => { - const { collection: collectionSlug, filename } = params - let req: PayloadRequest - let collection: Collection - - try { - req = await createPayloadRequest({ - config, - params: { collection: collectionSlug }, - request, - }) - - 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 && !collection.config.upload.handlers) { - throw new APIError( - `This collection has local storage disabled: ${collectionSlug}`, - httpStatus.BAD_REQUEST, - ) - } - - await checkFileAccess({ - collection, - filename, - req, - }) - - let response: Response = null - if (collection.config.upload.handlers?.length) { - for (const handler of collection.config.upload.handlers) { - response = await handler(req, { params }) - } - - return response - } - - 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, { - headers: new Headers({ - 'content-length': stats.size + '', - }), - status: httpStatus.OK, - }) - } catch (error) { - return RouteError({ - collection, - err: error, - req, - }) - } - } diff --git a/packages/next/src/routes/rest/files/checkFileAccess.ts b/packages/next/src/routes/rest/files/checkFileAccess.ts new file mode 100644 index 000000000..8bb8aca23 --- /dev/null +++ b/packages/next/src/routes/rest/files/checkFileAccess.ts @@ -0,0 +1,59 @@ +import type { Collection, PayloadRequest, Where } from 'payload/types' + +import { executeAccess } from 'payload/auth' +import { Forbidden } from 'payload/errors' + +import { endpointsAreDisabled } from '../checkEndpoints.js' + +export async function checkFileAccess({ + collection, + filename, + req, +}: { + collection: Collection + filename: string + req: PayloadRequest +}) { + const { config } = collection + const disableEndpoints = endpointsAreDisabled({ endpoints: config.endpoints, request: req }) + if (disableEndpoints) return disableEndpoints + + 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) + } + } +} diff --git a/packages/next/src/routes/rest/files/getFile.ts b/packages/next/src/routes/rest/files/getFile.ts new file mode 100644 index 000000000..4a0322401 --- /dev/null +++ b/packages/next/src/routes/rest/files/getFile.ts @@ -0,0 +1,73 @@ +import type { Collection, PayloadRequest } from 'payload/types' + +import fsPromises from 'fs/promises' +import httpStatus from 'http-status' +import path from 'path' +import { APIError } from 'payload/errors' + +import { streamFile } from '../../../next-stream-file/index.js' +import { RouteError } from '../RouteError.js' +import { checkFileAccess } from './checkFileAccess.js' + +// /:collectionSlug/file/:filename +type Args = { + collection: Collection + filename: string + req: PayloadRequest +} +export const getFile = async ({ collection, filename, req }: Args): Promise => { + try { + if (!collection.config.upload) { + throw new APIError( + `This collection is not an upload collection: ${collection.config.slug}`, + httpStatus.BAD_REQUEST, + ) + } + + if (collection.config.upload.disableLocalStorage && !collection.config.upload.handlers) { + throw new APIError( + `This collection has local storage disabled: ${collection.config.slug}`, + httpStatus.BAD_REQUEST, + ) + } + + await checkFileAccess({ + collection, + filename, + req, + }) + + let response: Response = null + if (collection.config.upload.handlers?.length) { + for (const handler of collection.config.upload.handlers) { + response = await handler(req, { + params: { + collection: collection.config.slug, + filename, + }, + }) + } + + return response + } + + 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, { + headers: new Headers({ + 'content-length': stats.size + '', + }), + status: httpStatus.OK, + }) + } catch (error) { + return RouteError({ + collection, + err: error, + req, + }) + } +} diff --git a/packages/next/src/routes/rest/index.ts b/packages/next/src/routes/rest/index.ts index 9d37464c3..758745c3b 100644 --- a/packages/next/src/routes/rest/index.ts +++ b/packages/next/src/routes/rest/index.ts @@ -37,6 +37,7 @@ import { findVersions } from './collections/findVersions.js' import { restoreVersion } from './collections/restoreVersion.js' import { update } from './collections/update.js' import { updateByID } from './collections/updateByID.js' +import { getFile } from './files/getFile.js' import { docAccess as docAccessGlobal } from './globals/docAccess.js' import { findOne } from './globals/findOne.js' import { findVersionByID as findVersionByIdGlobal } from './globals/findVersionByID.js' @@ -55,6 +56,7 @@ const endpoints = { 'doc-versions-by-id': findVersionByID, find, findByID, + getFile, init, me, versions: findVersions, @@ -205,7 +207,10 @@ export const GET = } break case 3: - if (`doc-${slug2}-by-id` in endpoints.collection.GET) { + if (slug2 === 'file') { + // /:collection/file/:filename + res = await endpoints.collection.GET.getFile({ collection, filename: slug3, req }) + } else if (`doc-${slug2}-by-id` in endpoints.collection.GET) { // /:collection/access/:id // /:collection/versions/:id