diff --git a/src/Minio/minio.service.ts b/src/Minio/minio.service.ts index 4792966..721a093 100644 --- a/src/Minio/minio.service.ts +++ b/src/Minio/minio.service.ts @@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config'; import { FileUpload } from 'graphql-upload/processRequest.js'; import { Client } from 'minio'; import { MINIO_CONNECTION } from 'nestjs-minio'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class MinioService { constructor( @@ -11,7 +12,8 @@ export class MinioService { ) {} async uploadFile(file: FileUpload, category: string) { - const { filename, mimetype, createReadStream, encoding } = await file; + const { mimetype, createReadStream, encoding } = await file; + const filename = this.fileName(); const Name = `${category}/${filename}`; const fileBuffer = createReadStream(); @@ -44,4 +46,10 @@ export class MinioService { fileName, ); } + + // + fileName() { + // generate a unique file name using uuid + return uuidv4(); + } } diff --git a/src/Resume/resume.schema.ts b/src/Resume/resume.schema.ts index c0f9a12..cb0b3e6 100644 --- a/src/Resume/resume.schema.ts +++ b/src/Resume/resume.schema.ts @@ -7,12 +7,13 @@ import { } from '@smatch-corp/nestjs-pothos'; import { Builder } from '../Graphql/graphql.builder'; import { PrismaService } from '../Prisma/prisma.service'; - +import { MinioService } from 'src/Minio/minio.service'; @Injectable() export class ResumeSchema extends PothosSchema { constructor( @Inject(SchemaBuilderToken) private readonly builder: Builder, private readonly prisma: PrismaService, + private readonly minioService: MinioService, ) { super(); } @@ -34,10 +35,31 @@ export class ResumeSchema extends PothosSchema { nullable: true, }), center: t.relation('center'), + resumeFile: t.relation('ResumeFile'), }), }); } + @PothosRef() + resumeFile() { + return this.builder.prismaObject('ResumeFile', { + fields: (t) => ({ + id: t.exposeID('id'), + resumeId: t.exposeID('resumeId'), + fileUrl: t.exposeString('fileUrl'), + type: t.exposeString('type'), + createdAt: t.expose('createdAt', { + type: 'DateTime', + nullable: true, + }), + updatedAt: t.expose('updatedAt', { + type: 'DateTime', + nullable: true, + }), + resume: t.relation('resume'), + }), + }); + } @Pothos() init(): void { this.builder.queryFields((t) => ({ @@ -59,10 +81,97 @@ export class ResumeSchema extends PothosSchema { type: this.resume(), args: this.builder.generator.findUniqueArgs('Resume'), resolve: async (query, root, args, ctx, info) => { - return await this.prisma.resume.findUnique({ + const resume = await this.prisma.resume.findUnique({ ...query, where: args.where, }); + return resume; + }, + }), + + resumeFile: t.prismaField({ + type: this.resumeFile(), + args: this.builder.generator.findUniqueArgs('ResumeFile'), + resolve: async (query, root, args, ctx, info) => { + const resumeFile = await this.prisma.resumeFile.findUnique({ + ...query, + where: args.where, + }); + if (!resumeFile) { + return null; + } + const resumeFileUrl = await this.minioService.getFileUrl( + resumeFile.fileUrl, + 'resumes', + ); + resumeFile.fileUrl = resumeFileUrl; + return resumeFile; + }, + }), + resumeFiles: t.prismaField({ + type: [this.resumeFile()], + args: this.builder.generator.findManyArgs('ResumeFile'), + resolve: async (query, root, args, ctx, info) => { + const resumeFiles = await this.prisma.resumeFile.findMany({ + ...query, + skip: args.skip ?? undefined, + take: args.take ?? 10, + orderBy: args.orderBy ?? undefined, + where: args.filter ?? undefined, + }); + const resumeFilesWithUrl = await Promise.all( + resumeFiles.map(async (resumeFile) => { + const resumeFileUrl = await this.minioService.getFileUrl( + resumeFile.fileUrl, + 'resumes', + ); + return { ...resumeFile, fileUrl: resumeFileUrl }; + }), + ); + return resumeFilesWithUrl; + }, + }), + })); + + // Mutations section + this.builder.mutationFields((t) => ({ + createResume: t.prismaField({ + type: this.resume(), + args: { + userId: t.arg({ + type: 'String', + required: true, + }), + centerId: t.arg({ + type: 'String', + required: true, + }), + resumeFile: t.arg({ + type: 'Upload', + required: true, + }), + }, + resolve: async (query, root, args, ctx, info) => { + const { userId, centerId, resumeFile } = args; + const { mimetype } = resumeFile; + const { filename } = await this.minioService.uploadFile( + resumeFile, + 'resumes', + ); + const resume = await this.prisma.resume.create({ + data: { + userId, + centerId, + ResumeFile: { + create: { + fileUrl: filename, + type: mimetype, + }, + }, + }, + }); + + return resume; }, }), })); diff --git a/src/UploadedFile/uploadedfile.schema.ts b/src/UploadedFile/uploadedfile.schema.ts index 0a2c3c3..018918c 100644 --- a/src/UploadedFile/uploadedfile.schema.ts +++ b/src/UploadedFile/uploadedfile.schema.ts @@ -41,23 +41,44 @@ export class UploadedFileSchema extends PothosSchema { type: this.uploadedFile(), args: this.builder.generator.findUniqueArgs('UploadedFile'), resolve: async (query, root, args, ctx, info) => { - return await this.prisma.uploadedFile.findUnique({ + const file = await this.prisma.uploadedFile.findUnique({ ...query, where: args.where, }); + if (!file) { + throw new Error('File not found'); + } + const fileUrl = await this.minioService.getFileUrl( + file.fileName, + 'files', + ); + if (!fileUrl) { + throw new Error('Cannot retrieve file url'); + } + file.fileUrl = fileUrl; + return file; }, }), uploadedDocuments: t.prismaField({ type: [this.uploadedFile()], args: this.builder.generator.findManyArgs('UploadedFile'), resolve: async (query, root, args, ctx, info) => { - return await this.prisma.uploadedFile.findMany({ + const files = await this.prisma.uploadedFile.findMany({ ...query, skip: args.skip ?? 0, take: args.take ?? 10, orderBy: args.orderBy ?? undefined, where: args.filter ?? undefined, }); + const fileUrls = await Promise.all( + files.map((file) => + this.minioService.getFileUrl(file.fileName, 'files'), + ), + ); + return files.map((file, index) => ({ + ...file, + fileUrl: fileUrls[index], + })); }, }), }));