cleanup decorators

This commit is contained in:
rubikscraft 2022-03-28 12:44:00 +02:00
parent d7d44b0147
commit a30cd8249a
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
6 changed files with 37 additions and 19 deletions

View file

@ -6,7 +6,14 @@ import { PostFilePipe } from './postfile.pipe';
@Module({ @Module({
imports: [EarlyConfigModule], imports: [EarlyConfigModule],
providers: [MultiPartPipe, PostFilePipe], providers: [MultiPartPipe, PostFilePipe],
exports: [MultiPartPipe, PostFilePipe, EarlyConfigModule],
exports: [
MultiPartPipe,
PostFilePipe,
// EarlyConfigModule is exported here because the pipes are dependedant on the config
// But these pipes dont resolve their dependencies via this module
// So this way we force it to be "global"
EarlyConfigModule,
],
}) })
export class DecoratorsModule { export class DecoratorsModule {}
}

View file

@ -0,0 +1,12 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Newable } from 'picsur-shared/dist/types';
// Since pipes dont have direct access to the request object, we need this decorator to inject it
export const InjectRequest = createParamDecorator(
async <T extends Object>(data: Newable<T>, ctx: ExecutionContext) => {
return {
req: ctx.switchToHttp().getRequest(),
data,
};
},
);

View file

@ -1,22 +1,9 @@
import {
createParamDecorator,
ExecutionContext
} from '@nestjs/common';
import { Newable } from 'picsur-shared/dist/types'; import { Newable } from 'picsur-shared/dist/types';
import { InjectRequest } from './injectrequest.decorator';
import { MultiPartPipe } from './multipart.pipe'; import { MultiPartPipe } from './multipart.pipe';
import { PostFilePipe } from './postfile.pipe'; import { PostFilePipe } from './postfile.pipe';
const InjectRequest = createParamDecorator( export const PostFile = () => InjectRequest(PostFilePipe);
async <T extends Object>(data: Newable<T>, ctx: ExecutionContext) => {
return {
req: ctx.switchToHttp().getRequest(),
data,
};
},
);
export const PostFile = () =>
InjectRequest(PostFilePipe);
export const MultiPart = <T extends Object>(data: Newable<T>) => export const MultiPart = <T extends Object>(data: Newable<T>) =>
InjectRequest(data, MultiPartPipe); InjectRequest(data, MultiPartPipe);

View file

@ -28,10 +28,12 @@ export class MultiPartPipe implements PipeTransform {
req: FastifyRequest; req: FastifyRequest;
data: Newable<T>; data: Newable<T>;
}) { }) {
// Data should be a validatable class constructor
const dtoClass = new data(); const dtoClass = new data();
if (!req.isMultipart()) throw new BadRequestException('Invalid file'); if (!req.isMultipart()) throw new BadRequestException('Invalid file');
// Fetch all fields from the request
let fields: MultipartFields | null = null; let fields: MultipartFields | null = null;
try { try {
fields = ( fields = (
@ -44,11 +46,15 @@ export class MultiPartPipe implements PipeTransform {
} }
if (!fields) throw new BadRequestException('Invalid file'); if (!fields) throw new BadRequestException('Invalid file');
// Loop over every formfield that was sent
for (const key of Object.keys(fields)) { for (const key of Object.keys(fields)) {
// Ignore duplicate fields
if (Array.isArray(fields[key])) { if (Array.isArray(fields[key])) {
continue; continue;
} }
// Use the value property to differentiate between a field and a file
// And then put the value into the correct property on the validatable class
if ((fields[key] as any).value) { if ((fields[key] as any).value) {
(dtoClass as any)[key] = new MultiPartFieldDto( (dtoClass as any)[key] = new MultiPartFieldDto(
fields[key] as MultipartFile, fields[key] as MultipartFile,
@ -61,6 +67,7 @@ export class MultiPartPipe implements PipeTransform {
} }
} }
// Now validate the class we made, if any properties were invalid, it will error here
const errors = await strictValidate(dtoClass); const errors = await strictValidate(dtoClass);
if (errors.length > 0) { if (errors.length > 0) {
this.logger.warn(errors); this.logger.warn(errors);

View file

@ -7,9 +7,10 @@ export const RequiredPermissions = (...permissions: Permissions) => {
return SetMetadata('permissions', permissions); return SetMetadata('permissions', permissions);
}; };
// Easy to read roles // Just a verbose wrapper
export const NoPermissions = () => RequiredPermissions(); export const NoPermissions = () => RequiredPermissions();
// This still requires permissions, but also allows the client to use user/pass authentication instead of JWT
export const UseLocalAuth = (...permissions: Permissions) => export const UseLocalAuth = (...permissions: Permissions) =>
CombineFCDecorators( CombineFCDecorators(
RequiredPermissions(...permissions), RequiredPermissions(...permissions),

View file

@ -18,6 +18,7 @@ export class PostFilePipe implements PipeTransform {
async transform({ req }: { req: FastifyRequest }) { async transform({ req }: { req: FastifyRequest }) {
if (!req.isMultipart()) throw new BadRequestException('Invalid file'); if (!req.isMultipart()) throw new BadRequestException('Invalid file');
// Only one file is allowed
const file = await req.file({ const file = await req.file({
limits: { limits: {
...this.multipartConfigService.getLimits(), ...this.multipartConfigService.getLimits(),
@ -26,14 +27,17 @@ export class PostFilePipe implements PipeTransform {
}); });
if (file === undefined) throw new BadRequestException('Invalid file'); if (file === undefined) throw new BadRequestException('Invalid file');
// Remove empty fields
const allFields: Multipart[] = Object.values(file.fields).filter( const allFields: Multipart[] = Object.values(file.fields).filter(
(entry) => entry, (entry) => entry,
) as any; ) as any;
// Remove non-file fields
const files = allFields.filter((entry) => entry.file !== undefined); const files = allFields.filter((entry) => entry.file !== undefined);
if (files.length !== 1) throw new BadRequestException('Invalid file'); if (files.length !== 1) throw new BadRequestException('Invalid file');
// Return a buffer of the file
try { try {
return await files[0].toBuffer(); return await files[0].toBuffer();
} catch (e) { } catch (e) {