diff --git a/apps/api/src/modules/cv-parser/cv-parser.module.ts b/apps/api/src/modules/cv-parser/cv-parser.module.ts index f93506a..5bb608c 100644 --- a/apps/api/src/modules/cv-parser/cv-parser.module.ts +++ b/apps/api/src/modules/cv-parser/cv-parser.module.ts @@ -12,7 +12,7 @@ import { DataImportModule } from "@/modules/data-import/data-import.module"; import { FileImportSource } from "@/modules/data-import/sources/file-import-source"; import { EducationModule } from "@/modules/education/education.module"; import { EmploymentModule } from "@/modules/job-experience/employment/employment.module"; -import { ProfileModule } from "@/modules/profile/profile.module"; +import { ProfileModule } from "@cv/core"; import { UserAiSettingsModule } from "@/modules/user-settings/user-ai-settings.module"; import { AIProviderResolverService } from "./ai-provider-resolver.service"; import { CVParserResolver } from "./cv-parser.resolver"; diff --git a/apps/api/src/modules/cv-parser/graphql/upload-file.resolver.ts b/apps/api/src/modules/cv-parser/graphql/upload-file.resolver.ts index 5d735e1..bd3f625 100644 --- a/apps/api/src/modules/cv-parser/graphql/upload-file.resolver.ts +++ b/apps/api/src/modules/cv-parser/graphql/upload-file.resolver.ts @@ -8,7 +8,7 @@ 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 { ProfileService } from "@cv/core"; import { UploadFileInput } from "./upload-file.input"; const MAX_DECODED_SIZE = 10 * 1024 * 1024; // 10MB diff --git a/apps/api/src/modules/cv-template/cv-data-assembler.service.ts b/apps/api/src/modules/cv-template/cv-data-assembler.service.ts index 877cdf2..5f1b001 100644 --- a/apps/api/src/modules/cv-template/cv-data-assembler.service.ts +++ b/apps/api/src/modules/cv-template/cv-data-assembler.service.ts @@ -1,6 +1,6 @@ import { PrismaService } from "@cv/core"; import { Injectable } from "@nestjs/common"; -import { ProfileService } from "@/modules/profile/profile.service"; +import { ProfileService } from "@cv/core"; export interface CVRenderContext { cv: { title: string; introduction: string | null }; diff --git a/apps/api/src/modules/cv-template/cv-template.module.ts b/apps/api/src/modules/cv-template/cv-template.module.ts index c183c18..9a6cf15 100644 --- a/apps/api/src/modules/cv-template/cv-template.module.ts +++ b/apps/api/src/modules/cv-template/cv-template.module.ts @@ -2,7 +2,7 @@ import { AuthorizationModule } from "@cv/core"; import { BaseModule, DatabaseModule } from "@cv/core"; import { Module, forwardRef } from "@nestjs/common"; import { AuthenticationModule } from "@/modules/authentication/authentication.module"; -import { ProfileModule } from "@/modules/profile/profile.module"; +import { ProfileModule } from "@cv/core"; import { CVDataAssemblerService } from "./cv-data-assembler.service"; import { CVRendererService } from "./cv-renderer.service"; import { CVDataLoaderService } from "./cv.dataloader"; diff --git a/apps/api/src/modules/data-import/data-import.module.ts b/apps/api/src/modules/data-import/data-import.module.ts index 2097d01..58c4fd8 100644 --- a/apps/api/src/modules/data-import/data-import.module.ts +++ b/apps/api/src/modules/data-import/data-import.module.ts @@ -2,7 +2,7 @@ import { AuthorizationModule } from "@cv/core"; import { DatabaseModule } from "@cv/core"; import { Module } from "@nestjs/common"; import { EntityResolverService } from "@/modules/cv-parser/entity-resolver.service"; -import { ProfileModule } from "@/modules/profile/profile.module"; +import { ProfileModule } from "@cv/core"; import { UserFileResolver } from "./graphql/user-file.resolver"; import { ImportService } from "./import.service"; import { ImportJobMapper } from "./import-job.mapper"; diff --git a/apps/api/src/modules/data-import/listeners/import-job.listener.ts b/apps/api/src/modules/data-import/listeners/import-job.listener.ts index 6bb4b66..689485a 100644 --- a/apps/api/src/modules/data-import/listeners/import-job.listener.ts +++ b/apps/api/src/modules/data-import/listeners/import-job.listener.ts @@ -7,7 +7,7 @@ import { type ResolvedEducation, type ResolvedJobExperience, } from "@/modules/cv-parser/entity-resolver.service"; -import { ProfileService } from "@/modules/profile/profile.service"; +import { ProfileService } from "@cv/core"; import { UserFileCreatedEvent } from "../events/user-file-created.event"; interface ResolvedResult { diff --git a/apps/api/src/modules/data-import/sources/file-import-source.ts b/apps/api/src/modules/data-import/sources/file-import-source.ts index fa6f575..470679b 100644 --- a/apps/api/src/modules/data-import/sources/file-import-source.ts +++ b/apps/api/src/modules/data-import/sources/file-import-source.ts @@ -10,7 +10,7 @@ import { Inject, Injectable, Logger } from "@nestjs/common"; import { EducationService } from "@/modules/education/education.service"; import { AIProviderResolverService } from "@/modules/cv-parser/ai-provider-resolver.service"; import { UserJobExperienceService } from "@/modules/job-experience/employment/user-job-experience.service"; -import { ProfileService } from "@/modules/profile/profile.service"; +import { ProfileService } from "@cv/core"; import type { DataImportSource } from "../data-import-source.interface"; /** diff --git a/apps/api/src/modules/profile/graphql/profile.resolver.ts b/apps/api/src/modules/profile/graphql/profile.resolver.ts index f916287..d3af8d5 100644 --- a/apps/api/src/modules/profile/graphql/profile.resolver.ts +++ b/apps/api/src/modules/profile/graphql/profile.resolver.ts @@ -1,9 +1,8 @@ -import type { User as DomainUser } from "@cv/core"; +import { type User as DomainUser, ProfileService } from "@cv/core"; import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; import { UseGuards } from "@nestjs/common"; import { Args, ID, Mutation, Query, Resolver } from "@nestjs/graphql"; import { CurrentUser } from "@/modules/current-user/current-user.decorator"; -import { ProfileService } from "../profile.service"; import { CreateProfileInput, ProfileType, diff --git a/apps/api/src/modules/profile/profile.entity.ts b/apps/api/src/modules/profile/profile.entity.ts deleted file mode 100644 index d5f7724..0000000 --- a/apps/api/src/modules/profile/profile.entity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { BaseEntity } from "@cv/core"; - -export class ProfileEntity extends BaseEntity { - constructor( - id: string, - public readonly userId: string, - public readonly name: string, - public readonly locationId: string | undefined, - createdAt: Date, - updatedAt: Date, - ) { - super(id, createdAt, updatedAt); - } -} diff --git a/apps/api/src/modules/profile/profile.module.ts b/apps/api/src/modules/profile/profile.module.ts index da43346..6ef76df 100644 --- a/apps/api/src/modules/profile/profile.module.ts +++ b/apps/api/src/modules/profile/profile.module.ts @@ -1,5 +1,10 @@ import { AuthModule } from "@cv/auth"; -import { BaseModule, DatabaseModule } from "@cv/core"; +import { + BaseModule, + DatabaseModule, + ProfilePolicy, + ProfileService, +} from "@cv/core"; import { Module, forwardRef } from "@nestjs/common"; import { CVTemplateModule } from "@/modules/cv-template/cv-template.module"; import { EducationModule } from "@/modules/education/education.module"; @@ -7,8 +12,6 @@ import { EmploymentModule } from "@/modules/job-experience/employment/employment import { ProfileFieldResolver } from "./graphql/profile-field.resolver"; import { ProfileResolver } from "./graphql/profile.resolver"; import { ProfileOnboardingStep } from "./onboarding/profile.step"; -import { ProfilePolicy } from "./profile.policy"; -import { ProfileService } from "./profile.service"; @Module({ imports: [ diff --git a/apps/api/src/modules/profile/profile.policy.ts b/apps/api/src/modules/profile/profile.policy.ts deleted file mode 100644 index 2670a40..0000000 --- a/apps/api/src/modules/profile/profile.policy.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Policy, UserOwnedResourcePolicy } from "@cv/core"; -import { Injectable } from "@nestjs/common"; -import { ProfileEntity } from "./profile.entity"; - -@Injectable() -@Policy(ProfileEntity) -export class ProfilePolicy extends UserOwnedResourcePolicy {} diff --git a/apps/api/src/modules/profile/profile.service.ts b/apps/api/src/modules/profile/profile.service.ts deleted file mode 100644 index bca9fb2..0000000 --- a/apps/api/src/modules/profile/profile.service.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { User } from "@cv/core"; -import { TokenEncryptionService } from "@cv/core"; -import { PrismaService } from "@cv/core"; -import { Injectable, NotFoundException } from "@nestjs/common"; - -interface CreateProfileInput { - name: string; - fullName?: string; - headline?: string; - phone?: string; - address?: string; - postalCode?: string; - locationId?: string; - website?: string; - linkedInUrl?: string; - summary?: string; -} - -interface UpdateProfileInput { - name?: string; - fullName?: string; - headline?: string; - phone?: string; - address?: string; - postalCode?: string; - locationId?: string; - website?: string; - linkedInUrl?: string; - summary?: string; -} - -export interface DecryptedProfile { - id: string; - userId: string; - name: string; - fullName: string | null; - headline: string | null; - phone: string | null; - address: string | null; - postalCode: string | null; - locationId: string | null; - website: string | null; - linkedInUrl: string | null; - summary: string | null; - createdAt: Date; - updatedAt: Date; -} - -@Injectable() -export class ProfileService { - constructor( - private readonly prisma: PrismaService, - private readonly encryption: TokenEncryptionService, - ) {} - - async createProfile( - userId: string, - data: CreateProfileInput, - ): Promise { - const encrypted = this.encryptFields(data); - - const profile = await this.prisma.profile.create({ - data: { - userId, - name: data.name, - ...encrypted, - }, - }); - - return this.decrypt(profile); - } - - async getProfilesForUser(userId: string): Promise { - const profiles = await this.prisma.profile.findMany({ - where: { userId }, - orderBy: { createdAt: "asc" }, - }); - - return profiles.map((p) => this.decrypt(p)); - } - - async findByIdOrFail(profileId: string): Promise { - const profile = await this.prisma.profile.findUnique({ - where: { id: profileId }, - }); - - if (!profile) throw new NotFoundException("Profile not found"); - - return this.decrypt(profile); - } - - async findByIdAndUserOrFail( - profileId: string, - userId: string, - ): Promise { - const profile = await this.prisma.profile.findFirst({ - where: { id: profileId, userId }, - }); - - if (!profile) throw new NotFoundException("Profile not found"); - - return this.decrypt(profile); - } - - async updateProfile( - profileId: string, - data: UpdateProfileInput, - ): Promise { - const encrypted = this.encryptFields(data); - - if (data.name !== undefined) { - encrypted["name"] = data.name; - } - - const profile = await this.prisma.profile.update({ - where: { id: profileId }, - data: encrypted, - }); - - return this.decrypt(profile); - } - - async deleteProfile(profileId: string): Promise { - await this.prisma.profile.delete({ - where: { id: profileId }, - }); - return true; - } - - async getOrCreateDefaultProfile(userId: string): Promise { - const existing = await this.prisma.profile.findFirst({ - where: { userId }, - orderBy: { createdAt: "asc" }, - }); - - if (existing) return this.decrypt(existing); - - return this.createProfile(userId, { name: "Default" }); - } - - async hasKeyFields(userId: string): Promise { - const profile = await this.prisma.profile.findFirst({ - where: { userId }, - select: { headline: true, locationId: true }, - }); - - return profile !== null && profile.headline !== null && profile.locationId !== null; - } - - private decrypt(profile: { - id: string; - userId: string; - name: string; - fullName: string | null; - headline: string | null; - encryptedPhone: string | null; - encryptedAddress: string | null; - encryptedPostalCode: string | null; - locationId: string | null; - website: string | null; - linkedInUrl: string | null; - summary: string | null; - createdAt: Date; - updatedAt: Date; - }): DecryptedProfile { - return { - id: profile.id, - userId: profile.userId, - name: profile.name, - fullName: profile.fullName, - headline: profile.headline, - phone: profile.encryptedPhone - ? this.encryption.decrypt(profile.encryptedPhone) - : null, - address: profile.encryptedAddress - ? this.encryption.decrypt(profile.encryptedAddress) - : null, - postalCode: profile.encryptedPostalCode - ? this.encryption.decrypt(profile.encryptedPostalCode) - : null, - locationId: profile.locationId, - website: profile.website, - linkedInUrl: profile.linkedInUrl, - summary: profile.summary, - createdAt: profile.createdAt, - updatedAt: profile.updatedAt, - }; - } - - private encryptFields( - data: Partial, - ): Record { - const result: Record = {}; - - if (data.fullName !== undefined) result["fullName"] = data.fullName || null; - if (data.headline !== undefined) result["headline"] = data.headline || null; - if (data.locationId !== undefined) - result["locationId"] = data.locationId || null; - if (data.website !== undefined) result["website"] = data.website || null; - if (data.linkedInUrl !== undefined) - result["linkedInUrl"] = data.linkedInUrl || null; - if (data.summary !== undefined) result["summary"] = data.summary || null; - - if (data.phone !== undefined) - result["encryptedPhone"] = data.phone - ? this.encryption.encrypt(data.phone) - : null; - if (data.address !== undefined) - result["encryptedAddress"] = data.address - ? this.encryption.encrypt(data.address) - : null; - if (data.postalCode !== undefined) - result["encryptedPostalCode"] = data.postalCode - ? this.encryption.encrypt(data.postalCode) - : null; - - return result; - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8126836..1434585 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,3 +3,4 @@ export * from "./modules/database"; export * from "./modules/mail"; export * from "./modules/auth"; export * from "./modules/events"; +export * from "./modules/profile"; diff --git a/packages/core/src/modules/profile/index.ts b/packages/core/src/modules/profile/index.ts new file mode 100644 index 0000000..5a1eece --- /dev/null +++ b/packages/core/src/modules/profile/index.ts @@ -0,0 +1,4 @@ +export { ProfileEntity } from "./profile.entity"; +export { ProfileModule } from "./profile.module"; +export { ProfilePolicy } from "./profile.policy"; +export { type DecryptedProfile, ProfileService } from "./profile.service";