because I got bored of customising my CV for every job
1import type { User } from "@cv/auth";
2import { PrismaService } from "@cv/system";
3import { Injectable } from "@nestjs/common";
4import { EventEmitter2 } from "@nestjs/event-emitter";
5import type { DataImportSource } from "./data-import-source.interface";
6import { UserFileCreatedEvent } from "./events/user-file-created.event";
7import { UserFile } from "./user-file.entity";
8import { UserFileMapper } from "./user-file.mapper";
9
10@Injectable()
11export class ImportService {
12 constructor(
13 private readonly prisma: PrismaService,
14 private readonly userFileMapper: UserFileMapper,
15 private readonly eventEmitter: EventEmitter2,
16 ) {}
17
18 /**
19 * Find an existing completed UserFile with the same fingerprint for this user.
20 */
21 async findDuplicateForUser(
22 userId: string,
23 fingerprint: string,
24 ): Promise<UserFile | null> {
25 const record = await this.prisma.userFile.findFirst({
26 where: {
27 fingerprint,
28 profile: { userId },
29 status: "completed",
30 },
31 orderBy: { createdAt: "desc" },
32 });
33 return this.userFileMapper.toDomain(record);
34 }
35
36 /**
37 * Create a UserFile and emit an event for async processing.
38 */
39 async createImport(
40 user: User,
41 profileId: string,
42 source: DataImportSource,
43 file: { fileName: string; mimeType: string; sizeBytes: number; fingerprint?: string },
44 params: Record<string, unknown>,
45 ): Promise<UserFile> {
46 const record = await this.prisma.userFile.create({
47 data: {
48 profile: { connect: { id: profileId } },
49 fingerprint: file.fingerprint ?? null,
50 fileName: file.fileName,
51 mimeType: file.mimeType,
52 sizeBytes: file.sizeBytes,
53 source: source.name,
54 status: "pending",
55 statusMessage: "Queued for processing",
56 },
57 });
58
59 this.eventEmitter.emit(
60 UserFileCreatedEvent.event,
61 new UserFileCreatedEvent(record.id, user, source, params),
62 );
63
64 return this.userFileMapper.toDomain(record) as UserFile;
65 }
66
67 /**
68 * Delete a UserFile and its cascading ImportJobs.
69 */
70 async deleteUserFile(id: string): Promise<void> {
71 await this.prisma.userFile.delete({ where: { id } });
72 }
73
74 async findUserFileById(id: string): Promise<UserFile | null> {
75 const record = await this.prisma.userFile.findUnique({ where: { id } });
76 return this.userFileMapper.toDomain(record);
77 }
78
79 async findUserFilesForUser(userId: string): Promise<UserFile[]> {
80 const records = await this.prisma.userFile.findMany({
81 where: { profile: { userId } },
82 orderBy: { createdAt: "desc" },
83 });
84 return this.userFileMapper.mapToDomain(records);
85 }
86}