because I got bored of customising my CV for every job
1import { randomBytes } from "node:crypto";
2import { mkdirSync } from "node:fs";
3import { join } from "node:path";
4import { CVParserModule as CVParserCoreModule } from "@cv/ai-parser";
5import { AIModule } from "@cv/ai-provider";
6import { FileExtractionModule } from "@cv/file-upload";
7import { DatabaseModule } from "@cv/system";
8import { Module } from "@nestjs/common";
9import { MulterModule } from "@nestjs/platform-express";
10import { diskStorage } from "multer";
11import { DataImportModule } from "@/modules/data-import/data-import.module";
12import { FileImportSource } from "@/modules/data-import/sources/file-import-source";
13import { EducationModule } from "@/modules/education/education.module";
14import { EmploymentModule } from "@/modules/job-experience/employment/employment.module";
15import { ProfileModule } from "@/modules/profile/profile.module";
16import { UserAiSettingsModule } from "@/modules/user-settings/user-ai-settings.module";
17import { AIProviderResolverService } from "./ai-provider-resolver.service";
18import { CVParserResolver } from "./cv-parser.resolver";
19import { CVParserService } from "./cv-parser.service";
20import { EntityResolverService } from "./entity-resolver.service";
21import { FileUploadController } from "./file-upload.controller";
22import { UploadFileResolver } from "./graphql/upload-file.resolver";
23import { ImportOnboardingStep } from "./onboarding/import.step";
24
25@Module({
26 imports: [
27 CVParserCoreModule.forConfig(),
28 AIModule.forConfig(),
29 FileExtractionModule.forRoot(),
30 DatabaseModule,
31 UserAiSettingsModule,
32 ProfileModule,
33 EmploymentModule,
34 EducationModule,
35 DataImportModule,
36 MulterModule.register({
37 storage: diskStorage({
38 destination: (_req, _file, cb) => {
39 const tmpDir = join(process.cwd(), "tmp", "cv-uploads");
40 mkdirSync(tmpDir, { recursive: true });
41 cb(null, tmpDir);
42 },
43 filename: (_req, file, cb) => {
44 const uniqueSuffix = `${Date.now()}-${randomBytes(8).toString("hex")}`;
45 const ext = file.originalname.split(".").pop() ?? "";
46 cb(null, `${uniqueSuffix}.${ext}`);
47 },
48 }),
49 limits: {
50 fileSize: 10 * 1024 * 1024, // 10MB
51 },
52 fileFilter: (_req, file, cb) => {
53 const allowedMimes = [
54 "application/pdf",
55 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
56 "text/plain",
57 "text/markdown",
58 ];
59
60 if (allowedMimes.includes(file.mimetype)) {
61 cb(null, true);
62 } else {
63 cb(
64 new Error(
65 `File type ${file.mimetype} not allowed. Supported: PDF, DOCX, TXT, MD`,
66 ),
67 false,
68 );
69 }
70 },
71 }),
72 ],
73 providers: [
74 EntityResolverService,
75 AIProviderResolverService,
76 CVParserService,
77 CVParserResolver,
78 UploadFileResolver,
79 ImportOnboardingStep,
80 FileImportSource,
81 ],
82 controllers: [FileUploadController],
83 exports: [CVParserService],
84})
85export class CVParserModule {}