-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #72 from getlarge/71-feat-create-lib-to-handle-fil…
…e-upload-for-fastify-based-apps feat: create lib to handle file upload for fastify based apps
- Loading branch information
Showing
40 changed files
with
1,773 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"extends": ["../../.eslintrc.json"], | ||
"ignorePatterns": ["!**/*"], | ||
"overrides": [ | ||
{ | ||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.ts", "*.tsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.json"], | ||
"parser": "jsonc-eslint-parser", | ||
"rules": { | ||
"@nx/dependency-checks": [ | ||
"error", | ||
{ | ||
"buildTargets": ["build"], | ||
"checkMissingDependencies": true, | ||
"checkObsoleteDependencies": true, | ||
"checkVersionMismatches": true | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# Fastify Upload | ||
|
||
[![npm][npm-image]][npm-url] | ||
|
||
[npm-image]: https://img.shields.io/npm/v/@getlarge/nestjs-tools-fastify-upload.svg?style=flat | ||
[npm-url]: https://npmjs.org/package/@getlarge/nestjs-tools-fastify-upload | ||
|
||
The Fastify Upload Service provides file upload support for NestJS applications using the Fastify adapter. | ||
It uses the [@fastify/multipart](https://www.npmjs.com/package/@fastify/multipart) module. | ||
|
||
The [nestjs-tools-file-storage](../file-storage/README.md) library can store the uploaded files in various storage backends. | ||
|
||
## Installation | ||
|
||
```bash | ||
$ npm install --save @getlarge/nestjs-tools-fastify-upload | ||
``` | ||
|
||
## Usage | ||
|
||
```ts | ||
import { Controller, Post, StreamableFile, UseInterceptors } from '@nestjs/common'; | ||
import { join } from 'node:path'; | ||
|
||
import { | ||
AnyFilesInterceptor, | ||
DiskStorage, | ||
DiskStorageFile, | ||
FileFieldsInterceptor, | ||
FileInterceptor, | ||
FilesInterceptor, | ||
MemoryStorage, | ||
MemoryStorageFile, | ||
StreamStorage, | ||
StreamStorageFile, | ||
UploadedFile, | ||
UploadedFiles, | ||
} from '@getlarge/nestjs-tools-fastify-upload'; | ||
|
||
@Controller() | ||
export class AppController { | ||
@Post('single') | ||
@UseInterceptors( | ||
FileInterceptor('file', { | ||
storage: new MemoryStorage(), | ||
}), | ||
) | ||
uploadSingleFile(@UploadedFile() file: MemoryStorageFile): { | ||
success: boolean; | ||
} { | ||
return { success: !!file }; | ||
} | ||
|
||
@Post('multiple') | ||
@UseInterceptors(FilesInterceptor('file', 10, { storage: new MemoryStorage() })) | ||
uploadMultipleFiles(@UploadedFiles() files: MemoryStorageFile[]): { | ||
success: boolean; | ||
fileCount: number; | ||
} { | ||
return { success: !!files.length, fileCount: files.length }; | ||
} | ||
|
||
@Post('any') | ||
@UseInterceptors(AnyFilesInterceptor({ storage: new MemoryStorage() })) | ||
uploadAnyFiles(@UploadedFiles() files: MemoryStorageFile[]): { | ||
success: boolean; | ||
fileCount: number; | ||
} { | ||
return { success: !!files.length, fileCount: files.length }; | ||
} | ||
|
||
@Post('fields') | ||
@UseInterceptors( | ||
FileFieldsInterceptor([{ name: 'profile' }, { name: 'avatar' }], { | ||
storage: new MemoryStorage(), | ||
}), | ||
) | ||
uploadFileFieldsFiles( | ||
@UploadedFiles() | ||
files: { | ||
profile?: MemoryStorageFile[]; | ||
avatar?: MemoryStorageFile[]; | ||
}, | ||
): { success: boolean; fileCount: number } { | ||
return { | ||
success: !!((files.profile?.length ?? 0) + (files.avatar?.length ?? 0)), | ||
fileCount: (files.profile?.length ?? 0) + (files.avatar?.length ?? 0), | ||
}; | ||
} | ||
|
||
@Post('single-stream') | ||
@UseInterceptors( | ||
FileInterceptor('file', { | ||
storage: new StreamStorage(), | ||
}), | ||
) | ||
streamSingleFile(@UploadedFile() file: StreamStorageFile): StreamableFile { | ||
return new StreamableFile(file.stream); | ||
} | ||
|
||
@Post('single-disk') | ||
@UseInterceptors( | ||
FileInterceptor('file', { | ||
storage: new DiskStorage({ | ||
removeAfter: true, | ||
}), | ||
dest: join(process.cwd(), 'uploads'), | ||
}), | ||
) | ||
persistSingleFile(@UploadedFile() file: DiskStorageFile): { | ||
success: boolean; | ||
filepath: string; | ||
} { | ||
return { success: !!file, filepath: file.path }; | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* eslint-disable */ | ||
export default { | ||
displayName: 'nestjs-tools-fastify-upload', | ||
preset: '../../jest.preset.js', | ||
testEnvironment: 'node', | ||
transform: { | ||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }], | ||
}, | ||
moduleFileExtensions: ['ts', 'js', 'html'], | ||
coverageDirectory: '../../coverage/packages/fastify-upload', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "@getlarge/nestjs-tools-fastify-upload", | ||
"version": "0.0.1", | ||
"description": "Fastify Upload utilies for NestJS", | ||
"keywords": [ | ||
"fastify", | ||
"file", | ||
"upload", | ||
"nestjs" | ||
], | ||
"license": "Apache-2.0", | ||
"author": "Edouard Maleix <[email protected]>", | ||
"homepage": "https://github.com/getlarge/nestjs-tools/tree/main/packages/fastify-upload", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"dependencies": { | ||
"tslib": "^2.3.0" | ||
}, | ||
"peerDependencies": { | ||
"@nestjs/common": "10", | ||
"fastify": "4", | ||
"@fastify/busboy": "2", | ||
"@fastify/multipart": "8", | ||
"rxjs": "7" | ||
}, | ||
"type": "commonjs", | ||
"main": "./src/index.js", | ||
"typings": "./src/index.d.ts" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "nestjs-tools-fastify-upload", | ||
"$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
"sourceRoot": "packages/fastify-upload/src", | ||
"projectType": "library", | ||
"tags": [], | ||
"targets": { | ||
"build": { | ||
"executor": "@nx/js:tsc", | ||
"outputs": ["{options.outputPath}"], | ||
"options": { | ||
"outputPath": "dist/packages/fastify-upload", | ||
"tsConfig": "packages/fastify-upload/tsconfig.lib.json", | ||
"packageJson": "packages/fastify-upload/package.json", | ||
"main": "packages/fastify-upload/src/index.ts", | ||
"assets": ["packages/fastify-upload/*.md"] | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './lib/decorators'; | ||
export * from './lib/interceptors'; | ||
export * from './lib/multipart'; | ||
export * from './lib/storage'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './uploaded-file.decorator'; | ||
export * from './uploaded-files.decorator'; |
9 changes: 9 additions & 0 deletions
9
packages/fastify-upload/src/lib/decorators/uploaded-file.decorator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; | ||
|
||
import { getMultipartRequest } from '../multipart/request'; | ||
import type { StorageFile } from '../storage/storage'; | ||
|
||
export const UploadedFile = createParamDecorator((data: unknown, ctx: ExecutionContext): StorageFile | undefined => { | ||
const req = getMultipartRequest(ctx.switchToHttp()); | ||
return req?.storageFile; | ||
}); |
11 changes: 11 additions & 0 deletions
11
packages/fastify-upload/src/lib/decorators/uploaded-files.decorator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; | ||
|
||
import { getMultipartRequest } from '../multipart/request'; | ||
import type { StorageFile } from '../storage/storage'; | ||
|
||
export const UploadedFiles = createParamDecorator( | ||
(data: unknown, ctx: ExecutionContext): Record<string, StorageFile[]> | StorageFile[] | undefined => { | ||
const req = getMultipartRequest(ctx.switchToHttp()); | ||
return req?.storageFiles; | ||
}, | ||
); |
31 changes: 31 additions & 0 deletions
31
packages/fastify-upload/src/lib/interceptors/any-files.interceptor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { CallHandler, ExecutionContext, mixin, NestInterceptor, Type } from '@nestjs/common'; | ||
import { Observable, tap } from 'rxjs'; | ||
|
||
import { handleMultipartAnyFiles } from '../multipart/handlers/any-files'; | ||
import { type TransformedUploadOptions, transformUploadOptions, type UploadOptions } from '../multipart/options'; | ||
import { getMultipartRequest } from '../multipart/request'; | ||
import type { Storage } from '../storage'; | ||
|
||
export function AnyFilesInterceptor<S extends Storage>(options?: UploadOptions<S>): Type<NestInterceptor> { | ||
class MixinInterceptor implements NestInterceptor { | ||
private readonly options: TransformedUploadOptions<S>; | ||
|
||
constructor() { | ||
this.options = transformUploadOptions<S>(options); | ||
} | ||
|
||
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<void>> { | ||
const ctx = context.switchToHttp(); | ||
const req = getMultipartRequest(ctx); | ||
|
||
const { body, files, remove } = await handleMultipartAnyFiles<S>(req, this.options); | ||
|
||
req.body = body; | ||
req.storageFiles = files; | ||
|
||
return next.handle().pipe(tap(remove)); | ||
} | ||
} | ||
|
||
return mixin(MixinInterceptor); | ||
} |
41 changes: 41 additions & 0 deletions
41
packages/fastify-upload/src/lib/interceptors/file-fields.interceptor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { CallHandler, ExecutionContext, mixin, NestInterceptor, Type } from '@nestjs/common'; | ||
import { Observable, tap } from 'rxjs'; | ||
|
||
import { | ||
handleMultipartFileFields, | ||
UploadField, | ||
UploadFieldMapEntry, | ||
uploadFieldsToMap, | ||
} from '../multipart/handlers/file-fields'; | ||
import { type TransformedUploadOptions, transformUploadOptions, type UploadOptions } from '../multipart/options'; | ||
import { getMultipartRequest } from '../multipart/request'; | ||
import type { Storage } from '../storage'; | ||
|
||
export function FileFieldsInterceptor<S extends Storage>( | ||
uploadFields: UploadField[], | ||
options?: UploadOptions<S>, | ||
): Type<NestInterceptor> { | ||
class MixinInterceptor implements NestInterceptor { | ||
private readonly options: TransformedUploadOptions<S>; | ||
private readonly fieldsMap: Map<string, UploadFieldMapEntry>; | ||
|
||
constructor() { | ||
this.options = transformUploadOptions(options); | ||
this.fieldsMap = uploadFieldsToMap(uploadFields); | ||
} | ||
|
||
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<void>> { | ||
const ctx = context.switchToHttp(); | ||
const req = getMultipartRequest(ctx); | ||
|
||
const { body, files, remove } = await handleMultipartFileFields(req, this.fieldsMap, this.options); | ||
|
||
req.body = body; | ||
req.storageFiles = files; | ||
|
||
return next.handle().pipe(tap(remove)); | ||
} | ||
} | ||
|
||
return mixin(MixinInterceptor); | ||
} |
32 changes: 32 additions & 0 deletions
32
packages/fastify-upload/src/lib/interceptors/file.interceptor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { CallHandler, ExecutionContext, mixin, NestInterceptor, Type } from '@nestjs/common'; | ||
import { Observable, tap } from 'rxjs'; | ||
|
||
import { handleMultipartSingleFile } from '../multipart/handlers/single-file'; | ||
import { type TransformedUploadOptions, transformUploadOptions, type UploadOptions } from '../multipart/options'; | ||
import { getMultipartRequest } from '../multipart/request'; | ||
import type { Storage } from '../storage'; | ||
|
||
export function FileInterceptor<S extends Storage>( | ||
fieldname: string, | ||
options?: UploadOptions<S>, | ||
): Type<NestInterceptor> { | ||
class MixinInterceptor implements NestInterceptor { | ||
private readonly options: TransformedUploadOptions<S>; | ||
|
||
constructor() { | ||
this.options = transformUploadOptions(options); | ||
} | ||
|
||
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<void>> { | ||
const ctx = context.switchToHttp(); | ||
const req = getMultipartRequest(ctx); | ||
|
||
const { file, body, remove } = await handleMultipartSingleFile(req, fieldname, this.options); | ||
req.body = body; | ||
req.storageFile = file; | ||
return next.handle().pipe(tap(remove)); | ||
} | ||
} | ||
|
||
return mixin(MixinInterceptor); | ||
} |
Oops, something went wrong.