diff --git a/src/app.module.ts b/src/app.module.ts index f873e86..a3fb16a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,11 +2,9 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthModule } from './routes/auth/auth.module'; import { UserEntity } from './collections/userdb/user.entity'; -import { UsersModule } from './collections/userdb/userdb.module'; import { ImageModule } from './routes/image/imageroute.module'; import Config from './env'; import { ImageEntity } from './collections/imagedb/image.entity'; -import { SafeImagesModule } from './lib/safeimages/safeimages.module'; @Module({ imports: [ @@ -22,9 +20,7 @@ import { SafeImagesModule } from './lib/safeimages/safeimages.module'; entities: [UserEntity, ImageEntity], }), AuthModule, - UsersModule, ImageModule, - SafeImagesModule, ], }) export class AppModule {} diff --git a/src/decorators/multipart.decorator.ts b/src/decorators/multipart.decorator.ts new file mode 100644 index 0000000..737b350 --- /dev/null +++ b/src/decorators/multipart.decorator.ts @@ -0,0 +1,38 @@ +import { + BadRequestException, + createParamDecorator, + ExecutionContext, +} from '@nestjs/common'; +import { FastifyRequest } from 'fastify'; +import { Multipart } from 'fastify-multipart'; +import { request } from 'http'; +import Config from 'src/env'; + +export interface MPFile { + fieldname: string; +} + +export const PostFile = createParamDecorator( + async (data: unknown, ctx: ExecutionContext) => { + const req: FastifyRequest = ctx.switchToHttp().getRequest(); + + if (!req.isMultipart()) throw new BadRequestException('Invalid file'); + + const file = await req.file({ + limits: { fileSize: Config.limits.maxFileSize, files: 1 }, + }); + if (file === undefined) throw new BadRequestException('Invalid file'); + + const allFields: Multipart[] = Object.values(file.fields).filter( + (entry) => entry, + ) as any; + + const files = allFields.filter((entry) => entry.file !== undefined); + + if (files.length !== 1) throw new BadRequestException('Invalid file'); + + return await files[0].toBuffer(); + }, +); + +// TODO: Make better multipart decoder diff --git a/src/env.ts b/src/env.ts index f3d707f..c22e8d1 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,7 +1,7 @@ const Config = { database: { host: process.env.DB_HOST ?? 'localhost', - port: parseInt(process.env.DB_PORT) ?? 5432, + port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 5432, username: process.env.DB_USERNAME ?? 'imagur', password: process.env.DB_PASSWORD ?? 'imagur', database: process.env.DB_DATABASE ?? 'imagur', @@ -14,6 +14,11 @@ const Config = { secret: process.env.JWT_SECRET ?? 'CHANGE_ME', expiresIn: process.env.JWT_EXPIRES_IN ?? '1d', }, + limits: { + maxFileSize: process.env.MAX_FILE_SIZE + ? parseInt(process.env.MAX_FILE_SIZE) + : 128000000, + }, }; export default Config; diff --git a/src/lib/safeimages/safeimages.module.ts b/src/lib/safeimages/safeimages.module.ts deleted file mode 100644 index 9128de3..0000000 --- a/src/lib/safeimages/safeimages.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { ImageDBModule } from 'src/collections/imagedb/imagedb.module'; -import { SafeImagesService } from './safeimages.service'; - -@Module({ - imports: [ImageDBModule], - providers: [SafeImagesService], - exports: [SafeImagesService], -}) -export class SafeImagesModule {} diff --git a/src/managers/imagemanager/imagemanager.module.ts b/src/managers/imagemanager/imagemanager.module.ts new file mode 100644 index 0000000..206ac62 --- /dev/null +++ b/src/managers/imagemanager/imagemanager.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ImageDBModule } from 'src/collections/imagedb/imagedb.module'; +import { ImageManagerService } from './imagemanager.service'; + +@Module({ + imports: [ImageDBModule], + providers: [ImageManagerService], + exports: [ImageManagerService], +}) +export class ImageManagerModule {} diff --git a/src/lib/safeimages/safeimages.service.ts b/src/managers/imagemanager/imagemanager.service.ts similarity index 97% rename from src/lib/safeimages/safeimages.service.ts rename to src/managers/imagemanager/imagemanager.service.ts index 2e3f597..8c732c6 100644 --- a/src/lib/safeimages/safeimages.service.ts +++ b/src/managers/imagemanager/imagemanager.service.ts @@ -6,7 +6,7 @@ import { FullMime, MimesService } from 'src/collections/imagedb/mimes.service'; import { AsyncFailable, Fail, HasFailed } from 'src/types/failable'; @Injectable() -export class SafeImagesService { +export class ImageManagerService { constructor( private readonly imagesService: ImageDBService, private readonly mimesService: MimesService, diff --git a/src/routes/image/imageroute.controller.ts b/src/routes/image/imageroute.controller.ts index 4586470..e07669a 100644 --- a/src/routes/image/imageroute.controller.ts +++ b/src/routes/image/imageroute.controller.ts @@ -10,13 +10,13 @@ import { Res, } from '@nestjs/common'; import { FastifyReply, FastifyRequest } from 'fastify'; -import { Multipart } from 'fastify-multipart'; +import { PostFile } from 'src/decorators/multipart.decorator'; +import { ImageManagerService } from 'src/managers/imagemanager/imagemanager.service'; import { HasFailed } from 'src/types/failable'; -import { SafeImagesService } from 'src/lib/safeimages/safeimages.service'; @Controller() export class ImageController { - constructor(private readonly imagesService: SafeImagesService) {} + constructor(private readonly imagesService: ImageManagerService) {} @Get('i/:hash') async getImage( @@ -35,27 +35,11 @@ export class ImageController { } @Post('i') - async uploadImage(@Req() req: FastifyRequest) { - if (!req.isMultipart()) - throw new BadRequestException('Not a multipart request'); - - const file = await req.file({ limits: {} }); - if (file === undefined) throw new BadRequestException('No file uploaded'); - - const allFields: Multipart[] = Object.values(file.fields).filter( - (entry) => entry, - ) as any; - - const options = allFields.filter((entry) => entry.file === undefined); - const files = allFields.filter((entry) => entry.file !== undefined); - - if (files.length !== 1) throw new BadRequestException('Invalid file'); - - const image = await files[0].toBuffer(); - - const hash = await this.imagesService.uploadImage(image); - if (HasFailed(hash)) + async uploadImage(@Req() req: FastifyRequest, @PostFile() file: Buffer) { + const hash = await this.imagesService.uploadImage(file); + if (HasFailed(hash)) { throw new InternalServerErrorException('Failed to upload image'); + } return { hash }; } diff --git a/src/routes/image/imageroute.module.ts b/src/routes/image/imageroute.module.ts index 2b501c1..984e780 100644 --- a/src/routes/image/imageroute.module.ts +++ b/src/routes/image/imageroute.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; -import { SafeImagesModule } from 'src/lib/safeimages/safeimages.module'; +import { ImageManagerModule } from 'src/managers/imagemanager/imagemanager.module'; + import { ImageController } from './imageroute.controller'; @Module({ - imports: [SafeImagesModule], + imports: [ImageManagerModule], controllers: [ImageController], }) export class ImageModule {}