Skip to content

Commit

Permalink
Add fastify support for gridfs files
Browse files Browse the repository at this point in the history
  • Loading branch information
pozylon committed Jan 2, 2025
1 parent 89b4cf9 commit 69bcee9
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 4 deletions.
3 changes: 2 additions & 1 deletion examples/minimal/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ UNCHAINED_CLOUD_ENDPOINT=https://engine.unchained.shop/graphql
UNCHAINED_TOKEN_SECRET=random-token-that-is-not-secret-at-all
UNCHAINED_COOKIE_SAMESITE=none
UNCHAINED_COOKIE_INSECURE=
UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET=secret
REDIS_DB=0
MONGOMS_VERSION=8.0.1
MONGOMS_VERSION=8.0.1
4 changes: 2 additions & 2 deletions examples/minimal/boot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { startPlatform, setAccessToken } from '@unchainedshop/platform';
import baseModules from '@unchainedshop/plugins/presets/base-modules.js';
// import connectBasePluginsToExpress from '@unchainedshop/plugins/presets/base-express.js';
import connectBasePluginsToFastify from '@unchainedshop/plugins/presets/base-fastify.js';
import { connect } from '@unchainedshop/api/lib/fastify/index.js';
import { createLogger } from '@unchainedshop/logger';
import seed from './seed.js';
Expand Down Expand Up @@ -47,7 +47,7 @@ const start = async () => {
await setAccessToken(engine.unchainedAPI, 'admin', 'secret');

await connect(fastify, engine);
// await connectBasePluginsToExpress(app);
await connectBasePluginsToFastify(fastify);

try {
await fastify.listen({ port: process.env.PORT ? parseInt(process.env.PORT) : 3000 });
Expand Down
118 changes: 118 additions & 0 deletions packages/plugins/src/files/gridfs/gridfs-webhook-fastify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { pipeline } from 'node:stream/promises';
import { PassThrough } from 'node:stream';
import { FastifyRequest, RouteHandlerMethod } from 'fastify';
import { buildHashedFilename } from '@unchainedshop/file-upload';
import sign from './sign.js';
import { configureGridFSFileUploadModule } from './index.js';
import { Context } from '@unchainedshop/api';
import { createLogger } from '@unchainedshop/logger';
import { getFileAdapter } from '@unchainedshop/core-files';

const logger = createLogger('unchained:plugins:gridfs');

export const gridfsHandler: RouteHandlerMethod = async (
req: FastifyRequest & {
unchainedContext: Context & {
modules: { gridfsFileUploads: ReturnType<typeof configureGridFSFileUploadModule> };
};
},
res,
) => {
try {
const { services, modules } = req.unchainedContext;
const { directoryName, fileName } = req.params as any;

/* This is a file upload endpoint, and thus we need to allow CORS.
else we'd need proxies for all kinds of things for storefronts */
res.header('Access-Control-Allow-Methods', 'GET, PUT');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Origin', '*');

if (req.method === 'OPTIONS') {
res.status(200);
return res.send();
}

if (req.method === 'PUT') {
const { s: signature, e: expiryTimestamp } = req.query as Record<string, string>;
const expiryDate = new Date(parseInt(expiryTimestamp as string, 10));
const fileId = await buildHashedFilename(directoryName, fileName, expiryDate);
if ((await sign(directoryName, fileId, expiryDate.getTime())) === signature) {
const file = await modules.files.findFile({ fileId });
if (file.expires === null) {
res.status(400);
return res.send('File already linked');
}
// If the type is octet-stream, prefer mimetype lookup from the filename
// Else prefer the content-type header
const type =
req.headers['Content-Type'] === 'application/octet-stream'
? file.type || (req.headers['Content-Type'] as string)
: (req.headers['Content-Type'] as string) || file.type;

const writeStream = await modules.gridfsFileUploads.createWriteStream(
directoryName,
fileId,
fileName,
{ 'content-type': type },
);

await pipeline(req.raw, new PassThrough(), writeStream);

const { length } = writeStream;
await services.files.linkFile({ fileId, size: length, type });

res.status(200);
return res.send();
}

res.status(403);
return res.send();
}

if (req.method === 'GET') {
const fileId = fileName;
const { s: signature, e: expiryTimestamp } = req.query as Record<string, string>;
const file = await modules.gridfsFileUploads.getFileInfo(directoryName, fileId);
const fileDocument = await modules.files.findFile({ fileId });
if (fileDocument?.meta?.isPrivate) {
const expiry = parseInt(expiryTimestamp as string, 10);
if (expiry <= Date.now()) {
res.status(403);
return res.send('Access restricted: Expired.');
}

const fileUploadAdapter = getFileAdapter();
const signedUrl = await fileUploadAdapter.createDownloadURL(fileDocument, expiry);

if (new URL(signedUrl, 'file://').searchParams.get('s') !== signature) {
res.status(403);
return res.send('Access restricted: Invalid signature.');
}
}
if (file?.metadata?.['content-type']) {
res.header('Content-Type', file.metadata['content-type']);
}
if (file?.length) {
res.header('Content-Length', file?.length.toString());
}

const readStream = await modules.gridfsFileUploads.createReadStream(directoryName, fileId);
res.status(200);
return res.send(readStream);
}

res.status(404);
return res.send();
} catch (e) {
if (e.code === 'ENOENT') {
logger.warn(e);
res.status(404);
return res.send(JSON.stringify({ name: e.name, code: e.code, message: e.message }));
} else {
logger.warn(e);
res.status(504);
return res.send(JSON.stringify({ name: e.name, code: e.code, message: e.message }));
}
}
};
2 changes: 1 addition & 1 deletion packages/plugins/src/presets/base-express.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { gridfsHandler } from '../files/gridfs/gridfs-webhook.js';
import { gridfsHandler } from '../files/gridfs/gridfs-webhook-express.js';

const { GRIDFS_PUT_SERVER_PATH = '/gridfs' } = process.env;

Expand Down
15 changes: 15 additions & 0 deletions packages/plugins/src/presets/base-fastify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FastifyInstance } from 'fastify';
import { gridfsHandler } from '../files/gridfs/gridfs-webhook-fastify.js';

const { GRIDFS_PUT_SERVER_PATH = '/gridfs/:directoryName/:fileName' } = process.env;

export default (fastify: FastifyInstance) => {
fastify.addContentTypeParser('*', function (req, payload, done) {
done(null);
});
fastify.route({
url: GRIDFS_PUT_SERVER_PATH,
method: ['GET', 'PUT', 'OPTIONS'],
handler: gridfsHandler,
});
};

0 comments on commit 69bcee9

Please sign in to comment.