because I got bored of customising my CV for every job
at main 79 lines 2.6 kB view raw
1import { createHash } from "node:crypto"; 2import type { User as DomainUser } from "@cv/auth"; 3import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4import { validateFile } from "@cv/file-upload"; 5import { BadRequestException, UseGuards } from "@nestjs/common"; 6import { Args, Mutation, Resolver } from "@nestjs/graphql"; 7import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 8import { UserFileType } from "@/modules/data-import/graphql/user-file.type"; 9import { ImportService } from "@/modules/data-import/import.service"; 10import { FileImportSource } from "@/modules/data-import/sources/file-import-source"; 11import { ProfileService } from "@/modules/profile/profile.service"; 12import { UploadFileInput } from "./upload-file.input"; 13 14const MAX_DECODED_SIZE = 10 * 1024 * 1024; // 10MB 15 16@Resolver() 17@UseGuards(JwtAuthGuard, VerifiedScopeGuard) 18export class UploadFileResolver { 19 constructor( 20 private readonly importService: ImportService, 21 private readonly fileImportSource: FileImportSource, 22 private readonly profileService: ProfileService, 23 ) {} 24 25 @Mutation(() => UserFileType) 26 async uploadFile( 27 @CurrentUser() user: DomainUser, 28 @Args("input") input: UploadFileInput, 29 ): Promise<UserFileType> { 30 const buffer = Buffer.from(input.content, "base64"); 31 32 if (buffer.byteLength > MAX_DECODED_SIZE) { 33 throw new BadRequestException( 34 `File exceeds maximum size of ${MAX_DECODED_SIZE / (1024 * 1024)}MB`, 35 ); 36 } 37 38 const validation = validateFile({ 39 buffer, 40 mimeType: input.mimeType, 41 originalName: input.fileName, 42 sizeBytes: buffer.byteLength, 43 }); 44 45 if (!validation.valid) { 46 throw new BadRequestException(validation.error); 47 } 48 49 const fingerprint = createHash("sha256").update(buffer).digest("hex"); 50 51 const duplicate = await this.importService.findDuplicateForUser( 52 user.id, 53 fingerprint, 54 ); 55 56 if (duplicate) { 57 return UserFileType.fromDomain(duplicate, { isDuplicate: true }); 58 } 59 60 const profile = input.profileId 61 ? await this.profileService.findByIdAndUserOrFail(input.profileId, user.id) 62 : await this.profileService.getOrCreateDefaultProfile(user.id); 63 64 const userFile = await this.importService.createImport( 65 user, 66 profile.id, 67 this.fileImportSource, 68 { 69 fileName: input.fileName, 70 mimeType: input.mimeType, 71 sizeBytes: buffer.byteLength, 72 fingerprint, 73 }, 74 { buffer, mimeType: input.mimeType }, 75 ); 76 77 return UserFileType.fromDomain(userFile); 78 } 79}