import { createHash } from "node:crypto"; import type { User as DomainUser } from "@cv/auth"; import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; import { validateFile } from "@cv/file-upload"; import { BadRequestException, UseGuards } from "@nestjs/common"; import { Args, Mutation, Resolver } from "@nestjs/graphql"; import { CurrentUser } from "@/modules/current-user/current-user.decorator"; import { UserFileType } from "@/modules/data-import/graphql/user-file.type"; import { ImportService } from "@/modules/data-import/import.service"; import { FileImportSource } from "@/modules/data-import/sources/file-import-source"; import { ProfileService } from "@/modules/profile/profile.service"; import { UploadFileInput } from "./upload-file.input"; const MAX_DECODED_SIZE = 10 * 1024 * 1024; // 10MB @Resolver() @UseGuards(JwtAuthGuard, VerifiedScopeGuard) export class UploadFileResolver { constructor( private readonly importService: ImportService, private readonly fileImportSource: FileImportSource, private readonly profileService: ProfileService, ) {} @Mutation(() => UserFileType) async uploadFile( @CurrentUser() user: DomainUser, @Args("input") input: UploadFileInput, ): Promise { const buffer = Buffer.from(input.content, "base64"); if (buffer.byteLength > MAX_DECODED_SIZE) { throw new BadRequestException( `File exceeds maximum size of ${MAX_DECODED_SIZE / (1024 * 1024)}MB`, ); } const validation = validateFile({ buffer, mimeType: input.mimeType, originalName: input.fileName, sizeBytes: buffer.byteLength, }); if (!validation.valid) { throw new BadRequestException(validation.error); } const fingerprint = createHash("sha256").update(buffer).digest("hex"); const duplicate = await this.importService.findDuplicateForUser( user.id, fingerprint, ); if (duplicate) { return UserFileType.fromDomain(duplicate, { isDuplicate: true }); } const profile = input.profileId ? await this.profileService.findByIdAndUserOrFail(input.profileId, user.id) : await this.profileService.getOrCreateDefaultProfile(user.id); const userFile = await this.importService.createImport( user, profile.id, this.fileImportSource, { fileName: input.fileName, mimeType: input.mimeType, sizeBytes: buffer.byteLength, fingerprint, }, { buffer, mimeType: input.mimeType }, ); return UserFileType.fromDomain(userFile); } }