diff --git a/examples/completed-retry-guard.ts b/examples/completed-retry-guard.ts new file mode 100644 index 00000000..2f74a3ee --- /dev/null +++ b/examples/completed-retry-guard.ts @@ -0,0 +1,54 @@ +import { File, UploadxResponse } from '@uploadx/core'; +import { RequestHandler } from 'express'; +/* eslint-disable @typescript-eslint/no-namespace */ +declare global { + namespace Express { + interface Response { + finishUpload: (uploadxResponse: UploadxResponse) => this; + } + } +} +/** + * + * @param busyResponse + * @param timeout seconds + */ +export const completedRetryGuard: ( + busyResponse: UploadxResponse, + timeout?: number +) => RequestHandler = (busyResponse, timeout = 100) => { + const lock: Record = {}; + + return async (req, res, next) => { + const { name: filename } = req.body as File; + function send(response: UploadxResponse) { + const { statusCode = 200, ...body } = response; + res.status(statusCode).json(body); + } + const done = () => + new Promise(resolve => { + let i = 0; + (function scan() { + if (lock[filename].statusCode !== busyResponse.statusCode || i++ > timeout) { + return resolve(lock[filename]); + } + setTimeout(scan, 1000); + })(); + }); + + res.finishUpload = (uploadxResponse: UploadxResponse) => { + lock[filename] = uploadxResponse; + return res; + }; + + if (lock[filename]) { + send(await done()); + return; + } + + lock[filename] = busyResponse; + // prevent resend last chunk + send(busyResponse); + next(); + }; +}; diff --git a/examples/express.ts b/examples/custom-validators.ts similarity index 86% rename from examples/express.ts rename to examples/custom-validators.ts index 41417df8..f09b1067 100644 --- a/examples/express.ts +++ b/examples/custom-validators.ts @@ -22,8 +22,7 @@ const onComplete: OnComplete> = file = return { statusCode: 200, message, - id: file.id, - headers: { ETag: file.id } + id: file.id }; }; @@ -34,11 +33,15 @@ const storage = new DiskStorage({ validation: { mime: { value: ['video/*'], response: [415, { message: 'video only' }] }, size: { - value: 500_000, + value: 500_000_000, isValid(file) { this.response = [ 412, - { message: `The file size(${file.size}) is larger than ${this.value as number} bytes` } + { + message: `File size(${file.size}) exceeds maximum permitted size of ${ + this.value as number + } bytes` + } ]; return file.size <= this.value; } diff --git a/examples/package.json b/examples/package.json index 522d2791..462a3579 100644 --- a/examples/package.json +++ b/examples/package.json @@ -4,13 +4,13 @@ "private": true, "scripts": { "basic": "tsnd -r tsconfig-paths/register -r dotenv/config express-basic", - "express": "tsnd -r tsconfig-paths/register -r dotenv/config express", + "custom-validators": "tsnd -r tsconfig-paths/register -r dotenv/config custom-validators", "gcs": "tsnd -r tsconfig-paths/register -r dotenv/config express-gcs", "gcs:direct": "tsnd -r tsconfig-paths/register -r dotenv/config gcs-direct", "node": "tsnd -r tsconfig-paths/register -r dotenv/config node-http-server", "s3": "tsnd -r tsconfig-paths/register -r dotenv/config express-s3", "tus": "tsnd -r tsconfig-paths/register -r dotenv/config express-tus", - "fastify": "tsnd -r tsconfig-paths/register -r dotenv/config fastify", + "processing": "tsnd -r tsconfig-paths/register -r dotenv/config processing", "express-polling": "tsnd -r tsconfig-paths/register -r dotenv/config express-polling" }, "dependencies": { diff --git a/examples/processing.ts b/examples/processing.ts new file mode 100644 index 00000000..41bccff3 --- /dev/null +++ b/examples/processing.ts @@ -0,0 +1,36 @@ +import { DiskFile, DiskStorage, Uploadx } from '@uploadx/core'; +import { createHash } from 'crypto'; +import * as express from 'express'; +import { createReadStream } from 'fs'; +import { join } from 'path'; +import { completedRetryGuard } from './completed-retry-guard'; + +const app = express(); + +const uploadDirectory = 'upload'; + +const fileHash: (filePath: string) => Promise = filePath => + new Promise(resolve => { + const hash = createHash('sha256'); + createReadStream(filePath) + .on('data', data => hash.update(data)) + .on('end', () => resolve(hash.digest('hex'))); + }); + +const storage = new DiskStorage({ directory: uploadDirectory }); + +const onComplete: express.RequestHandler = async (req, res, next) => { + const file = req.body as DiskFile; + try { + const sha256 = await fileHash(join(uploadDirectory, file.name)); + res.finishUpload({ statusCode: 200, body: { id: file.id, sha256 } }); + } catch (error) { + res.finishUpload({ statusCode: 422, body: { error: 'File hash calculation error' } }); + } +}; + +const uploadx = new Uploadx({ storage }); + +app.all('/files', uploadx.upload, completedRetryGuard({ statusCode: 202 }, 60), onComplete); + +app.listen(3002, () => console.log('listening on port:', 3002));