because I got bored of customising my CV for every job
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: sort imports + extract @cv/handlebars + service-locator pilot for PolicyRegistry

Three threads landed together: 1) Biome assist.organizeImports enabled; ran across the workspace. useImportType disabled globally because it breaks NestJS constructor-injection metadata (Nest needs the runtime class for DI; type-only imports erase to undefined). Per-package overrides cleaned up to point at apps/api (was apps/server, stale since CVG-38 rename) and to include packages/core, packages/handlers, packages/mail, packages/file-storage. 2) CVG-51 @cv/handlebars: hoisted createHandlebars() factory + helpers from cv-renderer to a new package. mail's HandlebarsTemplateService and cv-renderer's HandlebarsEngine both use it. Drops marked + dompurify from cv-renderer (now via @cv/handlebars). All 91 cv-renderer tests pass. 3) CVG-54 pilot: PolicyRegistry now uses @riotbyte-com/nest-service-locator's ServiceLocator + tagged services instead of hand-rolled DiscoveryService scan. @Policy() decorator now wraps the tag's decorator so existing call sites are unchanged. Verified end-to-end via docker compose: api boots clean, GraphQL responds, worker boots, schema generates.

+1573 -1507
+5 -2
apps/api/src/config/cors.configuration.ts
··· 1 - import type { INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { CorsConfigService } from "./cors.config"; 3 3 4 4 export const configureCors = (app: INestApplication): void => { ··· 7 7 const isDevelopment = corsConfig.isDevelopment(); 8 8 9 9 app.enableCors({ 10 - origin: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => { 10 + origin: ( 11 + origin: string | undefined, 12 + callback: (err: Error | null, allow?: boolean) => void, 13 + ) => { 11 14 if (!origin) { 12 15 return callback(null, true); 13 16 }
+4 -3
apps/api/src/config/csrf.middleware.ts
··· 1 - import type { NextFunction, Request, Response } from "express"; 2 - import type { CorsConfigService } from "./cors.config"; 1 + import { NextFunction, Request, Response } from "express"; 2 + import { CorsConfigService } from "./cors.config"; 3 3 4 4 const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]); 5 5 ··· 57 57 } 58 58 res.status(403).json({ 59 59 error: "Forbidden", 60 - message: "Origin or Referer header required for state-changing requests", 60 + message: 61 + "Origin or Referer header required for state-changing requests", 61 62 }); 62 63 return; 63 64 }
+6 -1
apps/api/src/config/env.validation.ts
··· 126 126 } 127 127 128 128 if (data.FILE_STORAGE_DRIVER === "r2") { 129 - for (const key of ["R2_ENDPOINT", "R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY", "R2_BUCKET"] as const) { 129 + for (const key of [ 130 + "R2_ENDPOINT", 131 + "R2_ACCESS_KEY_ID", 132 + "R2_SECRET_ACCESS_KEY", 133 + "R2_BUCKET", 134 + ] as const) { 130 135 if (!data[key]) { 131 136 ctx.addIssue({ 132 137 code: z.ZodIssueCode.custom,
+1 -9
apps/api/src/config/exception-to-http.pipe.ts
··· 4 4 EntityNotFoundError, 5 5 } from "@cv/core"; 6 6 import { 7 - ArgumentsHost, 8 - Catch, 9 - ConflictException, 10 - ExceptionFilter, 11 - ForbiddenException, 12 - Logger, 13 - NotFoundException, 14 - UnauthorizedException, 15 - } from "@nestjs/common"; 7 + type ArgumentsHost, Catch, ConflictException, ExceptionFilter, ForbiddenException, Logger, NotFoundException, UnauthorizedException, } from "@nestjs/common"; 16 8 import { DomainError } from "@/domain/errors/app-error"; 17 9 import { EntityAlreadyExistsError } from "@/domain/errors/conflict.error"; 18 10
+2 -5
apps/api/src/config/file-storage.config.ts
··· 1 + import { FileStorageConfigProvider, FileStorageModuleConfig } from "@cv/file-storage"; 1 2 import { Injectable } from "@nestjs/common"; 2 3 import { ConfigService } from "@nestjs/config"; 3 - import type { 4 - FileStorageConfigProvider, 5 - FileStorageModuleConfig, 6 - } from "@cv/file-storage"; 7 - import type { Env } from "./env.validation"; 4 + import { Env } from "./env.validation"; 8 5 9 6 @Injectable() 10 7 export class FileStorageConfig implements FileStorageConfigProvider {
+5 -3
apps/api/src/config/graphql-complexity.plugin.ts
··· 1 + import { ApolloServerPlugin, GraphQLRequestListener } from "@apollo/server"; 1 2 import { Plugin } from "@nestjs/apollo"; 2 - import type { ApolloServerPlugin, GraphQLRequestListener } from "@apollo/server"; 3 + import { GraphQLError } from "graphql"; 3 4 import { 4 5 fieldExtensionsEstimator, 5 6 getComplexity, 6 7 simpleEstimator, 7 8 } from "graphql-query-complexity"; 8 - import { GraphQLError } from "graphql"; 9 9 10 10 const MAX_COMPLEXITY = 1000; 11 11 const DEFAULT_FIELD_COMPLEXITY = 1; ··· 23 23 */ 24 24 @Plugin() 25 25 export class GraphQLComplexityPlugin implements ApolloServerPlugin { 26 - async requestDidStart(): Promise<GraphQLRequestListener<{ request: unknown }>> { 26 + async requestDidStart(): Promise< 27 + GraphQLRequestListener<{ request: unknown }> 28 + > { 27 29 return { 28 30 async didResolveOperation({ request, document, schema }) { 29 31 const complexity = getComplexity({
+6 -3
apps/api/src/config/helmet.configuration.ts
··· 1 - import type { ConfigService } from "@nestjs/config"; 2 - import type { HelmetOptions } from "helmet"; 1 + import { ConfigService } from "@nestjs/config"; 2 + import { HelmetOptions } from "helmet"; 3 3 4 4 /** 5 5 * Helmet config tuned for the cv-generator API. ··· 45 45 ], 46 46 fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"], 47 47 imgSrc: ["'self'", "data:", "blob:", "https:"], 48 - connectSrc: ["'self'", "https://apollo-server-landing-page.cdn.apollographql.com"], 48 + connectSrc: [ 49 + "'self'", 50 + "https://apollo-server-landing-page.cdn.apollographql.com", 51 + ], 49 52 // CV preview iframe uses srcDoc, not src; frameSrc 'self' covers any 50 53 // future same-origin embed. 51 54 frameSrc: ["'self'"],
+1 -1
apps/api/src/main.ts
··· 8 8 import { CorsConfigService } from "./config/cors.config"; 9 9 import { configureCors } from "./config/cors.configuration"; 10 10 import { createCsrfMiddleware } from "./config/csrf.middleware"; 11 - import { configureHelmet } from "./config/helmet.configuration"; 12 11 import { DomainExceptionFilter } from "./config/exception-to-http.pipe"; 12 + import { configureHelmet } from "./config/helmet.configuration"; 13 13 import { AppModule } from "./modules/app.module"; 14 14 import { GraphQLExceptionFilter } from "./modules/base/graphql-exception.filter"; 15 15
+1 -1
apps/api/src/modules/admin/admin.module.ts
··· 2 2 import { BaseModule } from "@cv/core"; 3 3 import { Module } from "@nestjs/common"; 4 4 import { ApplicationStatusModule } from "@/modules/application/application-status/application-status.module"; 5 + import { DatabaseModule } from "@/modules/database/database.module"; 5 6 import { EducationModule } from "@/modules/education/education.module"; 6 7 import { CompanyModule } from "@/modules/job-experience/company/company.module"; 7 8 import { LevelModule } from "@/modules/job-experience/level/level.module"; ··· 9 10 import { SkillModule } from "@/modules/job-experience/skill/skill.module"; 10 11 import { OrganizationModule } from "@/modules/organization/organization.module"; 11 12 import { JobTypeModule } from "@/modules/vacancies/job-type/job-type.module"; 12 - import { DatabaseModule } from "@/modules/database/database.module"; 13 13 import { AdminResolver } from "./admin.resolver"; 14 14 import { AdminLookupResolver } from "./graphql/admin-lookup.resolver"; 15 15 import { QueueMonitorService } from "./queue-monitor.service";
+5 -6
apps/api/src/modules/admin/admin.resolver.ts
··· 1 1 import { 2 - AI_PROVIDER, 3 - type AIProvider, 4 - registeredProviderTypes, 5 - } from "@cv/ai-provider"; 2 + AI_PROVIDER, AIProvider, registeredProviderTypes, } from "@cv/ai-provider"; 6 3 import { AdminGuard, JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 + import { PageInfo } from "@cv/core"; 7 5 import { Inject, Optional, UseGuards } from "@nestjs/common"; 8 - import { PageInfo } from "@cv/core"; 9 6 import { Args, Int, Query, Resolver } from "@nestjs/graphql"; 10 7 import { 11 8 AiCallLogEntryType, ··· 114 111 async queueMessages( 115 112 @Args("limit", { type: () => Int, nullable: true }) limit?: number, 116 113 ): Promise<InstanceType<typeof QueueMessageConnection>> { 117 - const result = await this.queueMonitorService.getMessages(limit ?? undefined); 114 + const result = await this.queueMonitorService.getMessages( 115 + limit ?? undefined, 116 + ); 118 117 return QueueMessageConnection.fromPaginationResult({ 119 118 edges: result.map((r, i) => ({ node: r, cursor: String(i) })), 120 119 pageInfo: new PageInfo(false, false, null, null),
+9 -9
apps/api/src/modules/admin/admin.type.ts
··· 1 1 import { Field, Float, ID, Int, ObjectType } from "@nestjs/graphql"; 2 2 import { createConnection } from "@/modules/base/connection.factory"; 3 - import type { 3 + import { 4 4 QueueMessageResult, 5 5 QueueStatsResult, 6 6 WorkerHealthResult, ··· 73 73 } 74 74 } 75 75 76 - export const { 77 - Connection: QueueMessageConnection, 78 - Edge: QueueMessageEdge, 79 - } = createConnection<QueueMessage, QueueMessageResult>( 80 - QueueMessage, 81 - QueueMessage.fromDomain, 82 - ); 76 + export const { Connection: QueueMessageConnection, Edge: QueueMessageEdge } = 77 + createConnection<QueueMessage, QueueMessageResult>( 78 + QueueMessage, 79 + QueueMessage.fromDomain, 80 + ); 83 81 84 - export type QueueMessageConnection = InstanceType<typeof QueueMessageConnection>; 82 + export type QueueMessageConnection = InstanceType< 83 + typeof QueueMessageConnection 84 + >; 85 85 export type QueueMessageEdge = InstanceType<typeof QueueMessageEdge>; 86 86 87 87 @ObjectType()
+1 -1
apps/api/src/modules/admin/ai-call-log-persistence.service.ts
··· 1 1 import { Injectable, Logger } from "@nestjs/common"; 2 2 import { PrismaService } from "@/modules/database/prisma.service"; 3 - import type { AiCallLogEntry } from "./ai-call-log.service"; 3 + import { AiCallLogEntry } from "./ai-call-log.service"; 4 4 5 5 interface QueryOptions { 6 6 limit?: number;
+1 -1
apps/api/src/modules/admin/ai-call-log.module.ts
··· 1 1 import { Global, Module } from "@nestjs/common"; 2 2 import { DatabaseModule } from "@/modules/database/database.module"; 3 - import { AiCallLogPersistenceService } from "./ai-call-log-persistence.service"; 4 3 import { AiCallLogService } from "./ai-call-log.service"; 4 + import { AiCallLogPersistenceService } from "./ai-call-log-persistence.service"; 5 5 6 6 @Global() 7 7 @Module({
+22 -40
apps/api/src/modules/admin/graphql/admin-lookup.resolver.ts
··· 1 1 import { AdminGuard, JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 - import type { NamedEntity } from "@cv/core"; 3 - import { ClockService, UuidFactoryService } from "@cv/core"; 2 + import { NamedEntity } from "@cv/core"; 3 + import { 4 + ApplicationStatus, ApplicationStatusService, ClockService, Company, CompanyService, Institution, InstitutionService, JobType, JobTypeService, Level, LevelService, Organization, OrganizationRole, OrganizationRoleService, OrganizationService, Role, RoleService, Skill, SkillService, UuidFactoryService, } from "@cv/core"; 4 5 import { UseGuards } from "@nestjs/common"; 5 6 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 6 - import { ApplicationStatusService } from "@cv/core"; 7 - import { ApplicationStatus } from "@cv/core"; 8 - import { SkillService } from "@cv/core"; 9 - import { Skill } from "@cv/core"; 10 - import { CompanyService } from "@cv/core"; 11 - import { Company } from "@cv/core"; 12 - import { RoleService } from "@cv/core"; 13 - import { Role } from "@cv/core"; 14 - import { LevelService } from "@cv/core"; 15 - import { Level } from "@cv/core"; 16 - import { InstitutionService } from "@cv/core"; 17 - import { Institution } from "@cv/core"; 18 - import { JobTypeService } from "@cv/core"; 19 - import { JobType } from "@cv/core"; 20 - import { OrganizationService } from "@cv/core"; 21 - import { Organization } from "@cv/core"; 22 - import { OrganizationRoleService } from "@cv/core"; 23 - import { OrganizationRole } from "@cv/core"; 24 7 import { AdminEntityType, AdminLookupEntity } from "./admin-lookup.type"; 25 8 26 9 type EntityLike = { ··· 97 80 ), 98 81 [AdminEntityType.ORGANIZATION]: { 99 82 findMany: (searchTerm) => 100 - organizationService.findMany().then((orgs) => 101 - searchTerm 102 - ? orgs.filter((o) => 103 - o.name.toLowerCase().includes(searchTerm.toLowerCase()), 104 - ) 105 - : orgs, 106 - ), 83 + organizationService 84 + .findMany() 85 + .then((orgs) => 86 + searchTerm 87 + ? orgs.filter((o) => 88 + o.name.toLowerCase().includes(searchTerm.toLowerCase()), 89 + ) 90 + : orgs, 91 + ), 107 92 findByIdOrFail: (id) => organizationService.findByIdOrFail(id), 108 93 save: (entity) => 109 94 organizationService.save(entity as unknown as Organization), ··· 114 99 }, 115 100 [AdminEntityType.ORGANIZATION_ROLE]: { 116 101 findMany: (searchTerm) => 117 - organizationRoleService.findAll().then((roles) => 118 - searchTerm 119 - ? roles.filter((r) => 120 - r.name.toLowerCase().includes(searchTerm.toLowerCase()), 121 - ) 122 - : roles, 123 - ), 102 + organizationRoleService 103 + .findAll() 104 + .then((roles) => 105 + searchTerm 106 + ? roles.filter((r) => 107 + r.name.toLowerCase().includes(searchTerm.toLowerCase()), 108 + ) 109 + : roles, 110 + ), 124 111 findByIdOrFail: (id) => organizationRoleService.findByIdOrFail(id), 125 112 save: async (entity) => { 126 113 const desc = ··· 163 150 ): Promise<AdminLookupEntity> { 164 151 const adapter = this.registry[entityType]; 165 152 const now = this.clock.now(); 166 - const entity = adapter.create( 167 - this.uuid.generate(), 168 - name, 169 - now, 170 - description, 171 - ); 153 + const entity = adapter.create(this.uuid.generate(), name, now, description); 172 154 const saved = await adapter.save(entity); 173 155 return AdminLookupEntity.fromDomain(saved); 174 156 }
+1 -6
apps/api/src/modules/admin/logging-ai-provider.ts
··· 1 1 import { randomUUID } from "node:crypto"; 2 - import type { 3 - AICompletionRequest, 4 - AICompletionResponse, 5 - AIProvider, 6 - AIProviderStatus, 7 - } from "@cv/ai-provider"; 2 + import { AICompletionRequest, AICompletionResponse, AIProvider, AIProviderStatus } from "@cv/ai-provider"; 8 3 import { AiCallLogService } from "./ai-call-log.service"; 9 4 10 5 interface LoggingOptions {
+1 -3
apps/api/src/modules/admin/queue-monitor.service.ts
··· 48 48 const HEALTHY_THRESHOLD_SECONDS = 60; 49 49 const STALE_THRESHOLD_SECONDS = 300; 50 50 51 - const deriveWorkerStatus = ( 52 - lastSeenAt: Date, 53 - ): "healthy" | "stale" | "dead" => { 51 + const deriveWorkerStatus = (lastSeenAt: Date): "healthy" | "stale" | "dead" => { 54 52 const ageSeconds = (Date.now() - lastSeenAt.getTime()) / 1000; 55 53 if (ageSeconds <= HEALTHY_THRESHOLD_SECONDS) return "healthy"; 56 54 if (ageSeconds <= STALE_THRESHOLD_SECONDS) return "stale";
+10 -5
apps/api/src/modules/app.module.ts
··· 1 - import { AuthorizationModule, EventsModule, UserModule } from "@cv/core"; 2 1 import { AuthModule } from "@cv/auth"; 2 + import { 3 + AuthorizationModule, 4 + BaseModule, 5 + DatabaseModule, 6 + EventsModule, 7 + UserModule, 8 + } from "@cv/core"; 3 9 import { FileStorageModule } from "@cv/file-storage"; 4 - import { BaseModule, DatabaseModule } from "@cv/core"; 5 10 import { ApolloDriver, type ApolloDriverConfig } from "@nestjs/apollo"; 6 11 import { Module } from "@nestjs/common"; 7 12 import { ConfigModule, ConfigService } from "@nestjs/config"; ··· 9 14 import { GraphQLModule } from "@nestjs/graphql"; 10 15 import { JwtModule } from "@nestjs/jwt"; 11 16 import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler"; 12 - import type { Request, Response } from "express"; 17 + import { Request, Response } from "express"; 13 18 import { AppConfigModule } from "@/config/config.module"; 14 19 import { validate } from "@/config/env.validation"; 15 20 import { FileStorageConfig } from "@/config/file-storage.config"; ··· 17 22 import { AdminModule } from "./admin/admin.module"; 18 23 import { AiCallLogModule } from "./admin/ai-call-log.module"; 19 24 import { AppModule as AppModuleComponent } from "./app/app.module"; 25 + import { ApplicationModule } from "./application/application.module"; 20 26 import { ApplicationStatusModule } from "./application/application-status/application-status.module"; 21 - import { ApplicationModule } from "./application/application.module"; 22 27 import { AuthenticationModule } from "./authentication/authentication.module"; 23 28 import { CurrentUserModule } from "./current-user/current-user.module"; 24 29 import { CVParserModule } from "./cv-parser/cv-parser.module"; ··· 31 36 import { LevelModule } from "./job-experience/level/level.module"; 32 37 import { RoleModule } from "./job-experience/role/role.module"; 33 38 import { SkillModule } from "./job-experience/skill/skill.module"; 39 + import { ProjectQMessengerModule } from "./messenger/messenger.module"; 34 40 import { OnboardingModule } from "./onboarding/onboarding.module"; 35 41 import { OrganizationModule } from "./organization/organization.module"; 36 42 import { ProfileModule } from "./profile/profile.module"; 37 43 import { UserAiSettingsModule } from "./user-settings/user-ai-settings.module"; 38 - import { ProjectQMessengerModule } from "./messenger/messenger.module"; 39 44 import { JobTypeModule } from "./vacancies/job-type/job-type.module"; 40 45 import { VacancyModule } from "./vacancies/vacancy.module"; 41 46
+1 -2
apps/api/src/modules/application/application-status.dataloader.ts
··· 1 - import { PrismaService } from "@cv/core"; 1 + import { ApplicationStatusRelation, PrismaService } from "@cv/core"; 2 2 import { Injectable, Scope } from "@nestjs/common"; 3 3 import DataLoader from "dataloader"; 4 - import type { ApplicationStatusRelation } from "@cv/core"; 5 4 6 5 @Injectable({ scope: Scope.REQUEST }) 7 6 export class ApplicationStatusDataLoaderService {
+7 -4
apps/api/src/modules/application/application-status/application-status.module.ts
··· 1 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + ApplicationStatusFactory, 3 + ApplicationStatusMapper, 4 + ApplicationStatusService, 5 + BaseModule, 6 + DatabaseModule, 7 + } from "@cv/core"; 2 8 import { Module } from "@nestjs/common"; 3 - import { ApplicationStatusFactory } from "@cv/core"; 4 - import { ApplicationStatusMapper } from "@cv/core"; 5 - import { ApplicationStatusService } from "@cv/core"; 6 9 7 10 @Module({ 8 11 imports: [DatabaseModule, BaseModule],
+7 -5
apps/api/src/modules/application/application.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 1 import { AuthModule } from "@cv/auth"; 3 - import { DatabaseModule } from "@cv/core"; 2 + import { 3 + ApplicationMapper, 4 + ApplicationPolicy, 5 + ApplicationService, 6 + AuthorizationModule, 7 + DatabaseModule, 8 + } from "@cv/core"; 4 9 import { Module } from "@nestjs/common"; 5 10 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 6 11 import { CVTemplateModule } from "@/modules/cv-template/cv-template.module"; 7 12 import { VacancyModule } from "@/modules/vacancies/vacancy.module"; 8 - import { ApplicationMapper } from "@cv/core"; 9 - import { ApplicationPolicy } from "@cv/core"; 10 - import { ApplicationService } from "@cv/core"; 11 13 import { ApplicationStatusDataLoaderService } from "./application-status.dataloader"; 12 14 import { ApplicationResolver } from "./graphql/application.resolver"; 13 15 import { ApplicationUserFieldResolver } from "./graphql/user-field.resolver";
+3 -6
apps/api/src/modules/application/graphql/application.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 - import { PrismaService } from "@cv/core"; 2 + import { User as DomainUser } from "@cv/core"; 3 + import { 4 + Application as ApplicationEntity, ApplicationMapper, ApplicationService, AuthorizationService, PrismaService, } from "@cv/core"; 5 5 import { UseGuards } from "@nestjs/common"; 6 6 import { 7 7 Args, ··· 16 16 import { CV } from "@/modules/cv-template/graphql/cv.type"; 17 17 import { Vacancy } from "@/modules/vacancies/graphql/vacancy.type"; 18 18 import { VacancyDataLoaderService } from "@/modules/vacancies/vacancy.dataloader"; 19 - import { Application as ApplicationEntity } from "@cv/core"; 20 - import { ApplicationMapper } from "@cv/core"; 21 - import { ApplicationService } from "@cv/core"; 22 19 import { ApplicationStatusDataLoaderService } from "../application-status.dataloader"; 23 20 import { 24 21 CreateApplicationInput,
+1 -1
apps/api/src/modules/application/graphql/application.type.ts
··· 1 + import { Application as ApplicationEntity } from "@cv/core"; 1 2 import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 3 import { GraphQLDate } from "graphql-scalars"; 3 4 import { createConnection } from "@/modules/base/connection.factory"; 4 - import { Application as ApplicationEntity } from "@cv/core"; 5 5 6 6 @ObjectType() 7 7 export class ApplicationStatus {
+1 -2
apps/api/src/modules/application/graphql/user-field.resolver.ts
··· 1 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 - import { PageInfo } from "@cv/core"; 2 + import { ApplicationService, PageInfo } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Parent, ResolveField, Resolver } from "@nestjs/graphql"; 5 5 import { User } from "@/modules/user/user.type"; 6 - import { ApplicationService } from "@cv/core"; 7 6 import { ApplicationConnection } from "./application.type"; 8 7 9 8 @Resolver(() => User)
+1 -2
apps/api/src/modules/authentication/authentication.module.ts
··· 1 1 import { AuthModule } from "@cv/auth"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 2 + import { AuthenticationService, BaseModule, DatabaseModule } from "@cv/core"; 3 3 import { Module } from "@nestjs/common"; 4 4 import { ConfigModule } from "@nestjs/config"; 5 - import { AuthenticationService } from "@cv/core"; 6 5 import { PasswordProviderModule } from "./providers/password/password-provider.module"; 7 6 import { AuthenticationController } from "./rest/authentication.controller"; 8 7 import { TokenResolver } from "./token/token.resolver";
+3 -3
apps/api/src/modules/authentication/providers/password/graphql/password-authentication.resolver.ts
··· 1 - import { AuthCookieService, AuthorizationService, type User as DomainUser, PasswordAuthenticationService } from "@cv/core"; 2 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 3 - import { RequestMetadata } from "@cv/core"; 2 + import { 3 + AuthCookieService, AuthorizationService, User as DomainUser, PasswordAuthenticationService, RequestMetadata, } from "@cv/core"; 4 4 import { UseGuards } from "@nestjs/common"; 5 5 import { Args, Context, Mutation, Resolver } from "@nestjs/graphql"; 6 6 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 7 7 import { User } from "@/modules/user/user.type"; 8 8 import { AuthenticationResponse } from "../../../graphql/authentication.type"; 9 - import type { GraphQLContext } from "../../../graphql/graphql-context.type"; 9 + import { GraphQLContext } from "../../../graphql/graphql-context.type"; 10 10 11 11 @Resolver() 12 12 export class PasswordAuthenticationResolver {
+1 -4
apps/api/src/modules/authentication/providers/password/password-provider.module.ts
··· 12 12 import { PasswordAuthenticationResolver } from "./graphql/password-authentication.resolver"; 13 13 14 14 @Module({ 15 - imports: [ 16 - ConfigModule, 17 - AuthPasswordProviderModule, 18 - ], 15 + imports: [ConfigModule, AuthPasswordProviderModule], 19 16 providers: [ 20 17 PasswordAuthenticationResolver, 21 18 {
+2 -3
apps/api/src/modules/authentication/rest/authentication.controller.ts
··· 1 - import { AuthCookieService } from "@cv/core"; 2 - import { RequestMetadata } from "@cv/core"; 1 + import { 2 + AuthCookieService, AuthenticationService, RequestMetadata, } from "@cv/core"; 3 3 import { Controller, Post, Res } from "@nestjs/common"; 4 4 import type { Response } from "express"; 5 - import { AuthenticationService } from "@cv/core"; 6 5 import { RefreshTokenCookie } from "../token/refresh-token-cookie.decorator"; 7 6 import { RefreshTokenResponseDto } from "../token/refresh-token-response.dto"; 8 7 import { TokenExpiration } from "../token/token-expiration.type";
+1 -1
apps/api/src/modules/authentication/token/refresh-token-cookie.decorator.ts
··· 1 1 import { createGqlParamDecorator, unauthorized } from "@cv/core"; 2 - import type { ExecutionContext } from "@nestjs/common"; 2 + import { ExecutionContext } from "@nestjs/common"; 3 3 import { GqlExecutionContext } from "@nestjs/graphql"; 4 4 import { z } from "zod/v4"; 5 5
+2 -2
apps/api/src/modules/authentication/token/token.resolver.ts
··· 1 - import { AuthCookieService, AuthorizationService, type User as DomainUser, RefreshToken, RefreshTokenService } from "@cv/core"; 2 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { AuthCookieService, AuthorizationService, User as DomainUser, RefreshToken, RefreshTokenService } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Args, Context, Mutation, Query, Resolver } from "@nestjs/graphql"; 5 5 import type { Response } from "express"; 6 6 import { CurrentRefreshTokenId } from "@/modules/current-user/current-refresh-token-id.decorator"; 7 7 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 8 - import type { GraphQLContext } from "../graphql/graphql-context.type"; 8 + import { GraphQLContext } from "../graphql/graphql-context.type"; 9 9 import { ActiveSession } from "./active-session.type"; 10 10 11 11 @Resolver()
+2 -2
apps/api/src/modules/base/connection.factory.ts
··· 1 - import { PageInfo, type PaginationResult } from "@cv/core"; 1 + import { PageInfo, PaginationResult } from "@cv/core"; 2 2 import { raise } from "@cv/utils"; 3 - import type { Type } from "@nestjs/common"; 3 + import { Type } from "@nestjs/common"; 4 4 import { Field, Int, ObjectType } from "@nestjs/graphql"; 5 5 6 6 export function createConnection<TNodeGql, TNodeDomain>(
+2 -2
apps/api/src/modules/base/graphql-exception.filter.ts
··· 1 1 import { 2 2 AuthenticationError, 3 3 AuthorizationError, 4 + DomainError, 4 5 EntityNotFoundError, 5 6 } from "@cv/core"; 6 - import { DomainError } from "@cv/core"; 7 7 import { 8 - ArgumentsHost, 8 + type ArgumentsHost, 9 9 Catch, 10 10 ConflictException, 11 11 ForbiddenException,
+2 -2
apps/api/src/modules/base/named-graphql-type.factory.ts
··· 1 - import type { NamedEntity } from "@cv/core"; 2 - import type { Type } from "@nestjs/common"; 1 + import { NamedEntity } from "@cv/core"; 2 + import { Type } from "@nestjs/common"; 3 3 import { Field, ObjectType } from "@nestjs/graphql"; 4 4 import { BaseGraphQLType } from "./base-graphql-type"; 5 5 import { createConnection } from "./connection.factory";
+1 -2
apps/api/src/modules/current-user/me.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { UserService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { User as DomainUser, UserService } from "@cv/core"; 4 3 import { UseGuards } from "@nestjs/common"; 5 4 import { Query, Resolver } from "@nestjs/graphql"; 6 5 import { User } from "@/modules/user/user.type";
+3 -3
apps/api/src/modules/cv-parser/__tests__/cv-parser.service.spec.ts
··· 1 1 import { readFileSync } from "node:fs"; 2 2 import { join } from "node:path"; 3 3 import { User } from "@cv/core"; 4 - import type { Mocked } from "vitest"; 5 - import type { AIProviderResolverService } from "../ai-provider-resolver.service"; 4 + import { Mocked } from "vitest"; 5 + import { AIProviderResolverService } from "../ai-provider-resolver.service"; 6 6 import { CVParserService } from "../cv-parser.service"; 7 - import type { EntityResolverService } from "../entity-resolver.service"; 7 + import { EntityResolverService } from "../entity-resolver.service"; 8 8 import { 9 9 mockJaneDoeParsedCV, 10 10 mockJohnSmithParsedCV,
+1 -1
apps/api/src/modules/cv-parser/__tests__/entity-resolver.service.spec.ts
··· 1 - import type { Mock } from "vitest"; 1 + import { Mock } from "vitest"; 2 2 import { EntityResolverService } from "../entity-resolver.service"; 3 3 4 4 type MockPrisma = {
+1 -1
apps/api/src/modules/cv-parser/__tests__/fixtures/mock-ai-responses.ts
··· 1 - import type { ParsedCVData } from "@cv/ai-parser"; 1 + import { ParsedCVData } from "@cv/ai-parser"; 2 2 3 3 /** 4 4 * Mock AI response for the plain text CV (John Smith)
+2 -6
apps/api/src/modules/cv-parser/ai-provider-resolver.service.ts
··· 1 1 import { randomUUID } from "node:crypto"; 2 2 import { 3 - AI_PROVIDER, 4 - type AIProvider, 5 - AnthropicProvider, 6 - OpenAIProvider, 7 - } from "@cv/ai-provider"; 8 - import type { User } from "@cv/core"; 3 + AI_PROVIDER, AIProvider, AnthropicProvider, OpenAIProvider, } from "@cv/ai-provider"; 4 + import { User } from "@cv/core"; 9 5 import { 10 6 BadRequestException, 11 7 ForbiddenException,
+1 -1
apps/api/src/modules/cv-parser/cv-parser.module.ts
··· 3 3 import { join } from "node:path"; 4 4 import { CVParserModule as CVParserCoreModule } from "@cv/ai-parser"; 5 5 import { AIModule } from "@cv/ai-provider"; 6 - import { FileExtractionModule } from "@cv/file-upload"; 7 6 import { DatabaseModule } from "@cv/core"; 7 + import { FileExtractionModule } from "@cv/file-upload"; 8 8 import { Module } from "@nestjs/common"; 9 9 import { MulterModule } from "@nestjs/platform-express"; 10 10 import { diskStorage } from "multer";
+1 -1
apps/api/src/modules/cv-parser/cv-parser.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 1 import { JwtAuthGuard } from "@cv/auth"; 2 + import { User as DomainUser } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Args, Mutation, Resolver } from "@nestjs/graphql"; 5 5 import { CurrentUser } from "../current-user/current-user.decorator";
+4 -11
apps/api/src/modules/cv-parser/cv-parser.service.ts
··· 1 - import { CVParserService as CVParser, type ParsedCVData } from "@cv/ai-parser"; 2 - import type { User } from "@cv/core"; 1 + import { CVParserService as CVParser, ParsedCVData } from "@cv/ai-parser"; 2 + import { User } from "@cv/core"; 3 3 import { 4 - TEXT_EXTRACTOR_REGISTRY, 5 - type TextExtractorRegistry, 6 - validateFile, 7 - } from "@cv/file-upload"; 4 + TEXT_EXTRACTOR_REGISTRY, TextExtractorRegistry, validateFile, } from "@cv/file-upload"; 8 5 import { Inject, Injectable } from "@nestjs/common"; 9 6 import { AIProviderResolverService } from "./ai-provider-resolver.service"; 10 - import { 11 - EntityResolverService, 12 - type ResolvedEducation, 13 - type ResolvedJobExperience, 14 - } from "./entity-resolver.service"; 7 + import { EntityResolverService, ResolvedEducation, ResolvedJobExperience } from "./entity-resolver.service"; 15 8 16 9 /** 17 10 * Parsed CV data with resolved entities
+9 -4
apps/api/src/modules/cv-parser/file-upload.controller.ts
··· 1 1 import { createHash } from "node:crypto"; 2 2 import { readFile, unlink } from "node:fs/promises"; 3 - import type { User as DomainUser } from "@cv/core"; 4 3 import { JwtAuthGuard } from "@cv/auth"; 4 + import { User as DomainUser } from "@cv/core"; 5 5 import { validateFile } from "@cv/file-upload"; 6 6 import { 7 7 BadRequestException, ··· 15 15 UseInterceptors, 16 16 } from "@nestjs/common"; 17 17 import { FileInterceptor } from "@nestjs/platform-express"; 18 - import { FileImportSource } from "@/modules/data-import/sources/file-import-source"; 19 18 import { ImportService } from "@/modules/data-import/import.service"; 19 + import { FileImportSource } from "@/modules/data-import/sources/file-import-source"; 20 20 21 21 @Controller("api/cv-parser") 22 22 export class FileUploadController { ··· 81 81 sizeBytes: userFile.sizeBytes, 82 82 }; 83 83 } catch (error) { 84 - this.logger.error(`File upload failed: ${error instanceof Error ? error.message : error}`, error instanceof Error ? error.stack : undefined); 84 + this.logger.error( 85 + `File upload failed: ${error instanceof Error ? error.message : error}`, 86 + error instanceof Error ? error.stack : undefined, 87 + ); 85 88 86 89 const message = 87 90 error instanceof Error ? error.message : "Failed to process file"; ··· 92 95 try { 93 96 await unlink(filePath); 94 97 } catch (err) { 95 - this.logger.warn(`Failed to clean up temporary file ${filePath}: ${err instanceof Error ? err.message : err}`); 98 + this.logger.warn( 99 + `Failed to clean up temporary file ${filePath}: ${err instanceof Error ? err.message : err}`, 100 + ); 96 101 } 97 102 } 98 103 }
+4 -1
apps/api/src/modules/cv-parser/graphql/upload-file.input.ts
··· 2 2 3 3 @InputType() 4 4 export class UploadFileInput { 5 - @Field(() => ID, { nullable: true, description: "Target profile. If omitted, auto-creates a Default profile." }) 5 + @Field(() => ID, { 6 + nullable: true, 7 + description: "Target profile. If omitted, auto-creates a Default profile.", 8 + }) 6 9 profileId?: string; 7 10 8 11 @Field()
+5 -3
apps/api/src/modules/cv-parser/graphql/upload-file.resolver.ts
··· 1 1 import { createHash } from "node:crypto"; 2 - import type { User as DomainUser } from "@cv/core"; 3 2 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 3 + import { User as DomainUser, ProfileService } from "@cv/core"; 4 4 import { validateFile } from "@cv/file-upload"; 5 5 import { BadRequestException, UseGuards } from "@nestjs/common"; 6 6 import { Args, Mutation, Resolver } from "@nestjs/graphql"; ··· 8 8 import { UserFileType } from "@/modules/data-import/graphql/user-file.type"; 9 9 import { ImportService } from "@/modules/data-import/import.service"; 10 10 import { FileImportSource } from "@/modules/data-import/sources/file-import-source"; 11 - import { ProfileService } from "@cv/core"; 12 11 import { UploadFileInput } from "./upload-file.input"; 13 12 14 13 const MAX_DECODED_SIZE = 10 * 1024 * 1024; // 10MB ··· 58 57 } 59 58 60 59 const profile = input.profileId 61 - ? await this.profileService.findByIdAndUserOrFail(input.profileId, user.id) 60 + ? await this.profileService.findByIdAndUserOrFail( 61 + input.profileId, 62 + user.id, 63 + ) 62 64 : await this.profileService.getOrCreateDefaultProfile(user.id); 63 65 64 66 const userFile = await this.importService.createImport(
+1 -2
apps/api/src/modules/cv-parser/onboarding/import.step.ts
··· 1 - import type { User } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 1 + import { PrismaService, User } from "@cv/core"; 3 2 import { Injectable } from "@nestjs/common"; 4 3 import { OnboardingStep } from "@/modules/onboarding/onboarding-step.decorator"; 5 4 import {
+1 -1
apps/api/src/modules/cv-parser/types/draft.types.ts
··· 1 1 import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 - import type { 2 + import { 3 3 DraftEntity, 4 4 ResolvedEducation, 5 5 ResolvedJobExperience,
+13 -10
apps/api/src/modules/cv-template/cv-template.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 3 - import { Module, forwardRef } from "@nestjs/common"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + CVDataAssemblerService, 5 + CVPolicy, 6 + CVRendererService, 7 + CVService, 8 + CVTemplatePolicy, 9 + CVTemplateService, 10 + DatabaseModule, 11 + PdfDownloadController, 12 + } from "@cv/core"; 13 + import { forwardRef, Module } from "@nestjs/common"; 4 14 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 15 import { ProfileModule } from "@/modules/profile/profile.module"; 6 - import { CVDataAssemblerService } from "@cv/core"; 7 - import { CVRendererService } from "@cv/core"; 8 16 import { CVDataLoaderService } from "./cv.dataloader"; 9 - import { CVPolicy } from "@cv/core"; 10 - import { CVService } from "@cv/core"; 11 - import { CVTemplatePolicy } from "@cv/core"; 12 - import { CVTemplateService } from "@cv/core"; 13 17 import { CVResolver, CVTemplateResolver } from "./graphql/cv-template.resolver"; 14 18 import { CVUserFieldResolver } from "./graphql/user-field.resolver"; 15 - import { PdfDownloadController } from "@cv/core"; 16 19 import { CVTemplateSeedService } from "./seed/cv-template.seed"; 17 20 18 21 @Module({
+1 -2
apps/api/src/modules/cv-template/cv.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "@cv/core"; 1 + import { BaseDataLoaderService, CVService } from "@cv/core"; 2 2 import { Injectable, Scope } from "@nestjs/common"; 3 - import { CVService } from "@cv/core"; 4 3 import { CV } from "./graphql/cv.type"; 5 4 6 5 @Injectable({ scope: Scope.REQUEST })
+2 -7
apps/api/src/modules/cv-template/graphql/cv-template.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 - import { PaginationService, UuidFactoryService } from "@cv/core"; 2 + import { AuthorizationService, CVRendererService, CVService, CVTemplateService, User as DomainUser, PaginationService, UuidFactoryService } from "@cv/core"; 5 3 import { UseGuards } from "@nestjs/common"; 6 4 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 7 - import type { MessageBus } from "@riotbyte-com/project-q-core"; 5 + import { MessageBus } from "@riotbyte-com/project-q-core"; 8 6 import { InjectMessageBus } from "@riotbyte-com/project-q-nestjs"; 9 7 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 10 8 import { RenderPdfMessage } from "@/modules/messenger/messages/render-pdf.message"; 11 - import { CVRendererService } from "@cv/core"; 12 - import { CVService } from "@cv/core"; 13 - import { CVTemplateService } from "@cv/core"; 14 9 import { CV } from "./cv.type"; 15 10 import { CreateCVInput, UpdateCVInput } from "./cv-input.type"; 16 11 import {
+1 -2
apps/api/src/modules/cv-template/graphql/user-field.resolver.ts
··· 1 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 - import { PaginationService, PrismaService } from "@cv/core"; 2 + import { CVService, PaginationService, PrismaService } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Args, Parent, ResolveField, Resolver } from "@nestjs/graphql"; 5 5 import { User } from "@/modules/user/user.type"; 6 - import { CVService } from "@cv/core"; 7 6 import { CVConnection } from "./cv.type"; 8 7 import { CVArgs } from "./cv-args.type"; 9 8
+2 -2
apps/api/src/modules/data-import/data-import-source.interface.ts
··· 1 - import type { ParsedCVData } from "@cv/ai-parser"; 2 - import type { User } from "@cv/core"; 1 + import { ParsedCVData } from "@cv/ai-parser"; 2 + import { User } from "@cv/core"; 3 3 4 4 export interface DataImportSource { 5 5 readonly name: string;
+1 -2
apps/api/src/modules/data-import/data-import.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { DatabaseModule } from "@cv/core"; 1 + import { AuthorizationModule, DatabaseModule } from "@cv/core"; 3 2 import { Module } from "@nestjs/common"; 4 3 import { EntityResolverService } from "@/modules/cv-parser/entity-resolver.service"; 5 4 import { ProfileModule } from "@/modules/profile/profile.module";
+2 -2
apps/api/src/modules/data-import/events/user-file-created.event.ts
··· 1 + import { User } from "@cv/core"; 1 2 import { DomainEvent } from "@cv/core"; 2 - import type { User } from "@cv/core"; 3 - import type { DataImportSource } from "../data-import-source.interface"; 3 + import { DataImportSource } from "../data-import-source.interface"; 4 4 5 5 interface UserFileCreatedPayload { 6 6 userFileId: string;
+1 -2
apps/api/src/modules/data-import/graphql/user-file.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { AuthorizationService, User as DomainUser } from "@cv/core"; 4 3 import { UseGuards } from "@nestjs/common"; 5 4 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 6 5 import { CurrentUser } from "@/modules/current-user/current-user.decorator";
+1 -1
apps/api/src/modules/data-import/graphql/user-file.type.ts
··· 1 1 import { Field, Int, ObjectType } from "@nestjs/graphql"; 2 2 import GraphQLJSON from "graphql-type-json"; 3 - import type { UserFile as UserFileDomain } from "../user-file.entity"; 3 + import { UserFile as UserFileDomain } from "../user-file.entity"; 4 4 5 5 @ObjectType() 6 6 export class UserFileType {
+2 -2
apps/api/src/modules/data-import/import-job.mapper.ts
··· 1 - import type { BaseMapper } from "@cv/core"; 1 + import { BaseMapper } from "@cv/core"; 2 2 import { Injectable } from "@nestjs/common"; 3 - import type { Prisma } from "@prisma/client"; 3 + import { Prisma } from "@prisma/client"; 4 4 5 5 type PrismaImportJob = Prisma.ImportJobGetPayload<object>; 6 6
+1 -1
apps/api/src/modules/data-import/import-job.policy.ts
··· 1 - import type { IPolicy, User } from "@cv/core"; 1 + import { IPolicy, User } from "@cv/core"; 2 2 import { Policy } from "@cv/core"; 3 3 import { Injectable } from "@nestjs/common"; 4 4 import { ImportJob } from "./import-job.entity";
+8 -5
apps/api/src/modules/data-import/import.service.ts
··· 1 - import type { User } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 3 - import { EventService } from "@cv/core"; 1 + import { EventService, PrismaService, User } from "@cv/core"; 4 2 import { Injectable } from "@nestjs/common"; 5 - import type { DataImportSource } from "./data-import-source.interface"; 3 + import { DataImportSource } from "./data-import-source.interface"; 6 4 import { UserFileCreatedEvent } from "./events/user-file-created.event"; 7 5 import { UserFile } from "./user-file.entity"; 8 6 import { UserFileMapper } from "./user-file.mapper"; ··· 40 38 user: User, 41 39 profileId: string, 42 40 source: DataImportSource, 43 - file: { fileName: string; mimeType: string; sizeBytes: number; fingerprint?: string }, 41 + file: { 42 + fileName: string; 43 + mimeType: string; 44 + sizeBytes: number; 45 + fingerprint?: string; 46 + }, 44 47 params: Record<string, unknown>, 45 48 ): Promise<UserFile> { 46 49 const record = await this.prisma.userFile.create({
+19 -13
apps/api/src/modules/data-import/listeners/import-job.listener.ts
··· 1 - import type { ParsedCVData } from "@cv/ai-parser"; 2 - import { PrismaService } from "@cv/core"; 1 + import { ParsedCVData } from "@cv/ai-parser"; 2 + import { PrismaService, ProfileService } from "@cv/core"; 3 3 import { Injectable, Logger } from "@nestjs/common"; 4 4 import { OnEvent } from "@nestjs/event-emitter"; 5 - import { 6 - EntityResolverService, 7 - type ResolvedEducation, 8 - type ResolvedJobExperience, 9 - } from "@/modules/cv-parser/entity-resolver.service"; 10 - import { ProfileService } from "@cv/core"; 5 + import { EntityResolverService, type ResolvedEducation, type ResolvedJobExperience } from "@/modules/cv-parser/entity-resolver.service"; 11 6 import { UserFileCreatedEvent } from "../events/user-file-created.event"; 12 7 13 8 interface ResolvedResult { ··· 75 70 `${tag} Parsed: ${parsed.jobExperiences.length} job(s), ` + 76 71 `${parsed.education.length} education(s), ${parsed.skills.length} skill(s)`, 77 72 ); 78 - this.logger.debug(`${tag} Parsed result: ${JSON.stringify(parsed, null, 2)}`); 73 + this.logger.debug( 74 + `${tag} Parsed result: ${JSON.stringify(parsed, null, 2)}`, 75 + ); 79 76 80 77 await this.updateStatusMessage(jobId, userFileId, "Resolving entities"); 81 78 const resolved = await this.resolveEntities(parsed); ··· 90 87 await this.completeJob(jobId, userFileId, resolved); 91 88 } catch (err) { 92 89 const message = err instanceof Error ? err.message : "Processing failed"; 93 - this.logger.error(`${tag} Import failed: ${message}`, err instanceof Error ? err.stack : undefined); 90 + this.logger.error( 91 + `${tag} Import failed: ${message}`, 92 + err instanceof Error ? err.stack : undefined, 93 + ); 94 94 await this.failJob(jobId, userFileId, message); 95 95 } 96 96 } ··· 174 174 } 175 175 176 176 if (Object.keys(updates).length === 0) { 177 - this.logger.debug(`${tag} Profile already has data, skipping auto-populate`); 177 + this.logger.debug( 178 + `${tag} Profile already has data, skipping auto-populate`, 179 + ); 178 180 return; 179 181 } 180 182 181 183 await this.profileService.updateProfile(profileId, updates); 182 - this.logger.log(`${tag} Auto-populated profile: ${Object.keys(updates).join(", ")}`); 184 + this.logger.log( 185 + `${tag} Auto-populated profile: ${Object.keys(updates).join(", ")}`, 186 + ); 183 187 } catch (err) { 184 - this.logger.warn(`${tag} Failed to auto-populate profile (non-fatal): ${err instanceof Error ? err.message : err}`); 188 + this.logger.warn( 189 + `${tag} Failed to auto-populate profile (non-fatal): ${err instanceof Error ? err.message : err}`, 190 + ); 185 191 } 186 192 } 187 193
+39 -16
apps/api/src/modules/data-import/sources/file-import-source.ts
··· 1 1 import { 2 2 CVParserService as CVParser, 3 - type ExistingUserContext, 4 - type ParsedCVData, 3 + ExistingUserContext, 4 + ParsedCVData, 5 5 } from "@cv/ai-parser"; 6 - import type { User } from "@cv/core"; 7 - import { PrismaService } from "@cv/core"; 8 - import { TextExtractorRegistry, TEXT_EXTRACTOR_REGISTRY } from "@cv/file-upload"; 6 + import { EducationService, PrismaService, ProfileService, User, UserJobExperienceService } from "@cv/core"; 7 + import { 8 + TEXT_EXTRACTOR_REGISTRY, TextExtractorRegistry, } from "@cv/file-upload"; 9 9 import { Inject, Injectable, Logger } from "@nestjs/common"; 10 - import { EducationService } from "@cv/core"; 11 10 import { AIProviderResolverService } from "@/modules/cv-parser/ai-provider-resolver.service"; 12 - import { UserJobExperienceService } from "@cv/core"; 13 - import { ProfileService } from "@cv/core"; 14 - import type { DataImportSource } from "../data-import-source.interface"; 11 + import { DataImportSource } from "../data-import-source.interface"; 15 12 16 13 /** 17 14 * Build ExistingUserContext from domain models for AI prompt enrichment. 18 15 */ 19 16 const buildExistingUserContext = ( 20 17 profile: { fullName?: string | null; headline?: string | null } | null, 21 - jobs: Array<{ company: { name: string }; role: { name: string }; startDate: Date; endDate?: Date }>, 22 - educations: Array<{ institution: { name: string }; degree: string; startDate: Date; endDate: Date | null }>, 18 + jobs: Array<{ 19 + company: { name: string }; 20 + role: { name: string }; 21 + startDate: Date; 22 + endDate?: Date; 23 + }>, 24 + educations: Array<{ 25 + institution: { name: string }; 26 + degree: string; 27 + startDate: Date; 28 + endDate: Date | null; 29 + }>, 23 30 ): ExistingUserContext | undefined => { 24 31 const context: ExistingUserContext = {}; 25 32 ··· 28 35 29 36 if (jobs.length > 0) { 30 37 context.jobs = jobs.map((j) => { 31 - const entry: { company: string; role: string; startDate: string; endDate?: string } = { 38 + const entry: { 39 + company: string; 40 + role: string; 41 + startDate: string; 42 + endDate?: string; 43 + } = { 32 44 company: j.company.name, 33 45 role: j.role.name, 34 46 startDate: j.startDate.toISOString().slice(0, 10), ··· 40 52 41 53 if (educations.length > 0) { 42 54 context.education = educations.map((e) => { 43 - const entry: { institution: string; degree: string; startDate: string; endDate?: string } = { 55 + const entry: { 56 + institution: string; 57 + degree: string; 58 + startDate: string; 59 + endDate?: string; 60 + } = { 44 61 institution: e.institution.name, 45 62 degree: e.degree, 46 63 startDate: e.startDate.toISOString().slice(0, 10), ··· 96 113 throw new Error("Could not extract any text from the file"); 97 114 } 98 115 99 - this.logger.log(`Extracted ${extraction.text.length} chars from ${mimeType}`); 100 - this.logger.debug(`Extracted text preview: ${extraction.text.slice(0, 500)}`); 116 + this.logger.log( 117 + `Extracted ${extraction.text.length} chars from ${mimeType}`, 118 + ); 119 + this.logger.debug( 120 + `Extracted text preview: ${extraction.text.slice(0, 500)}`, 121 + ); 101 122 102 123 await onStatus("Gathering existing profile data"); 103 124 ··· 114 135 return result; 115 136 } 116 137 117 - private async gatherUserContext(user: User): Promise<ExistingUserContext | undefined> { 138 + private async gatherUserContext( 139 + user: User, 140 + ): Promise<ExistingUserContext | undefined> { 118 141 try { 119 142 const firstProfile = await this.prisma.profile.findFirst({ 120 143 where: { userId: user.id },
+2 -2
apps/api/src/modules/data-import/user-file.mapper.ts
··· 1 - import type { BaseMapper } from "@cv/core"; 1 + import { BaseMapper } from "@cv/core"; 2 2 import { Injectable } from "@nestjs/common"; 3 - import type { Prisma } from "@prisma/client"; 3 + import { Prisma } from "@prisma/client"; 4 4 5 5 type PrismaUserFile = Prisma.UserFileGetPayload<object>; 6 6
+2 -2
apps/api/src/modules/data-import/user-file.policy.ts
··· 1 - import { Policy, ProfileOwnedResourcePolicy } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 1 + import { 2 + Policy, PrismaService, ProfileOwnedResourcePolicy, } from "@cv/core"; 3 3 import { Injectable } from "@nestjs/common"; 4 4 import { UserFile } from "./user-file.entity"; 5 5
+3 -3
apps/api/src/modules/database/seed/seed.service.ts
··· 1 1 import { PrismaService } from "@cv/core"; 2 - import { Injectable, Logger, OnModuleInit } from "@nestjs/common"; 2 + import { Injectable, Logger, type OnModuleInit } from "@nestjs/common"; 3 3 import { DiscoveryService } from "@nestjs/core"; 4 - import type { SeederConfig } from "./seeder.decorator"; 4 + import { SeederConfig } from "./seeder.decorator"; 5 5 import { SEEDER_METADATA_KEY } from "./seeder.decorator"; 6 6 import { topologicalSort } from "./topological-sort"; 7 7 ··· 42 42 const providers = this.discoveryService.getProviders(); 43 43 44 44 for (const wrapper of providers) { 45 - if (!wrapper.isDependencyTreeStatic() || !wrapper.instance) continue; 45 + if (!(wrapper.isDependencyTreeStatic() && wrapper.instance)) continue; 46 46 47 47 const instance = wrapper.instance; 48 48 const seederMetadata = Reflect.getMetadata(
+2 -6
apps/api/src/modules/database/seed/topological-sort.spec.ts
··· 71 71 72 72 it("throws on unknown dependency names", () => { 73 73 expect(() => 74 - topologicalSort([ 75 - { name: "A", dependsOn: ["NonExistent"], value: 1 }, 76 - ]), 74 + topologicalSort([{ name: "A", dependsOn: ["NonExistent"], value: 1 }]), 77 75 ).toThrow("Unknown seeder dependencies: NonExistent"); 78 76 }); 79 77 80 78 it("preserves values through sorting", () => { 81 79 const obj = { data: "test" }; 82 - const result = topologicalSort([ 83 - { name: "A", dependsOn: [], value: obj }, 84 - ]); 80 + const result = topologicalSort([{ name: "A", dependsOn: [], value: obj }]); 85 81 86 82 expect("sorted" in result).toBe(true); 87 83 if (!("sorted" in result)) return;
+12 -9
apps/api/src/modules/education/education.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + DatabaseModule, 5 + EducationMapper, 6 + EducationPolicy, 7 + EducationService, 8 + InstitutionFactory, 9 + InstitutionMapper, 10 + InstitutionPolicy, 11 + InstitutionService, 12 + } from "@cv/core"; 3 13 import { Module } from "@nestjs/common"; 4 14 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 15 import { SkillModule } from "@/modules/job-experience/skill/skill.module"; 6 - import { EducationMapper } from "@cv/core"; 7 - import { EducationPolicy } from "@cv/core"; 8 - import { EducationService } from "@cv/core"; 9 16 import { EducationResolver } from "./graphql/education.resolver"; 10 17 import { InstitutionResolver } from "./graphql/institution.resolver"; 11 18 import { EducationUserFieldResolver } from "./graphql/user-field.resolver"; 12 - import { InstitutionFactory } from "@cv/core"; 13 - import { InstitutionMapper } from "@cv/core"; 14 - import { InstitutionPolicy } from "@cv/core"; 15 - import { InstitutionService } from "@cv/core"; 16 19 import { EducationOnboardingStep } from "./onboarding/education.step"; 17 20 18 21 @Module({
+3 -6
apps/api/src/modules/education/graphql/education.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { User as DomainUser } from "@cv/core"; 3 + import { 4 + AuthorizationService, Education as EducationEntity, EducationService, InstitutionService, SkillService, } from "@cv/core"; 4 5 import { UseGuards } from "@nestjs/common"; 5 6 import { Args, Mutation, Resolver } from "@nestjs/graphql"; 6 7 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 7 - import { InstitutionService } from "@cv/core"; 8 - import { SkillService } from "@cv/core"; 9 - import { Education as EducationEntity } from "@cv/core"; 10 - import { EducationService } from "@cv/core"; 11 8 import { Education } from "./education.type"; 12 9 13 10 @Resolver(() => Education)
+4 -2
apps/api/src/modules/education/graphql/education.type.ts
··· 1 + import { 2 + Education as EducationEntity, 3 + Education as EducationEntityType, 4 + } from "@cv/core"; 1 5 import { Field, ObjectType } from "@nestjs/graphql"; 2 6 import { createConnection } from "@/modules/base/connection.factory"; 3 7 import { Skill } from "@/modules/job-experience/skill/graphql/skill.type"; 4 - import type { Education as EducationEntityType } from "@cv/core"; 5 - import { Education as EducationEntity } from "@cv/core"; 6 8 import { Institution } from "./institution.type"; 7 9 8 10 @ObjectType()
+3 -5
apps/api/src/modules/education/graphql/institution.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { User as DomainUser } from "@cv/core"; 3 + import { 4 + AuthorizationService, Institution as InstitutionEntity, InstitutionFactory, InstitutionService, } from "@cv/core"; 4 5 import { UseGuards } from "@nestjs/common"; 5 6 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 6 7 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 7 - import { Institution as InstitutionEntity } from "@cv/core"; 8 - import { InstitutionFactory } from "@cv/core"; 9 - import { InstitutionService } from "@cv/core"; 10 8 import { Institution } from "./institution.type"; 11 9 12 10 @Resolver(() => Institution)
+1 -1
apps/api/src/modules/education/graphql/institution.type.ts
··· 1 - import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 2 1 import { Institution as InstitutionDomain } from "@cv/core"; 2 + import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 3 3 4 4 const { Type, Connection, Edge } = createNamedGraphQLType( 5 5 "Institution",
+1 -2
apps/api/src/modules/education/graphql/user-field.resolver.ts
··· 1 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 - import { PaginationArgs, PaginationService, PrismaService } from "@cv/core"; 2 + import { EducationService, PaginationArgs, PaginationService, PrismaService } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Args, Parent, ResolveField, Resolver } from "@nestjs/graphql"; 5 5 import { User } from "@/modules/user/user.type"; 6 - import { EducationService } from "@cv/core"; 7 6 import { EducationConnection } from "./education.type"; 8 7 9 8 @Resolver(() => User)
+1 -2
apps/api/src/modules/education/onboarding/education.step.ts
··· 1 - import type { User } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 1 + import { PrismaService, User } from "@cv/core"; 3 2 import { Injectable } from "@nestjs/common"; 4 3 import { OnboardingStep } from "@/modules/onboarding/onboarding-step.decorator"; 5 4 import {
+2 -3
apps/api/src/modules/job-experience/company/company.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "@cv/core"; 1 + import { 2 + BaseDataLoaderService, Company, CompanyService, } from "@cv/core"; 2 3 import { Injectable, Scope } from "@nestjs/common"; 3 - import { Company } from "@cv/core"; 4 - import { CompanyService } from "@cv/core"; 5 4 6 5 @Injectable({ scope: Scope.REQUEST }) 7 6 export class CompanyDataLoaderService extends BaseDataLoaderService<
+9 -6
apps/api/src/modules/job-experience/company/company.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + CompanyFactory, 5 + CompanyMapper, 6 + CompanyPolicy, 7 + CompanyService, 8 + DatabaseModule, 9 + } from "@cv/core"; 3 10 import { Module } from "@nestjs/common"; 4 11 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 - import { CompanyService } from "@cv/core"; 6 12 import { CompanyDataLoaderService } from "./company.dataloader"; 7 - import { CompanyFactory } from "@cv/core"; 8 - import { CompanyMapper } from "@cv/core"; 9 - import { CompanyPolicy } from "@cv/core"; 10 13 import { CompanyResolver } from "./graphql/company.resolver"; 11 14 12 15 @Module({
+3 -9
apps/api/src/modules/job-experience/company/graphql/company.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 - import { PaginationService } from "@cv/core"; 2 + import { User as DomainUser } from "@cv/core"; 3 + import { 4 + AuthorizationService, Company, Company as CompanyEntity, CompanyFactory, CompanyService, PaginationService, } from "@cv/core"; 5 5 import { UseGuards } from "@nestjs/common"; 6 6 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 7 7 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 8 - import { 9 - Company, 10 - Company as CompanyEntity, 11 - } from "@cv/core"; 12 - import { CompanyFactory } from "@cv/core"; 13 - import { CompanyService } from "@cv/core"; 14 8 import { CompanyConnection, Company as CompanyGraphQL } from "./company.type"; 15 9 import { CompanyConnectionArgs } from "./company-connection-args.type"; 16 10
+4 -2
apps/api/src/modules/job-experience/company/graphql/company.type.ts
··· 1 + import { 2 + Company as CompanyDomain, 3 + Company as DomainCompany, 4 + } from "@cv/core"; 1 5 import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 6 import { createConnection } from "@/modules/base/connection.factory"; 3 - import type { Company as DomainCompany } from "@cv/core"; 4 - import { Company as CompanyDomain } from "@cv/core"; 5 7 6 8 @ObjectType() 7 9 export class Company {
+8 -5
apps/api/src/modules/job-experience/employment/employment.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + DatabaseModule, 5 + UserJobExperienceMapper, 6 + UserJobExperiencePolicy, 7 + UserJobExperienceService, 8 + } from "@cv/core"; 3 9 import { Module } from "@nestjs/common"; 4 10 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 11 import { CompanyModule } from "@/modules/job-experience/company/company.module"; ··· 9 15 import { EmploymentResolver } from "./graphql/employment.resolver"; 10 16 import { UserFieldResolver } from "./graphql/user-field.resolver"; 11 17 import { CareerHistoryOnboardingStep } from "./onboarding/career-history.step"; 12 - import { UserJobExperienceMapper } from "@cv/core"; 13 - import { UserJobExperiencePolicy } from "@cv/core"; 14 - import { UserJobExperienceService } from "@cv/core"; 15 18 16 19 @Module({ 17 20 imports: [
+7 -12
apps/api/src/modules/job-experience/employment/graphql/employment.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { 3 + CreateUserJobExperienceDto, 4 + User as DomainUser, 5 + UpdateUserJobExperienceDto, 6 + } from "@cv/core"; 7 + import { 8 + AuthorizationService, CompanyService, LevelService, RoleService, SkillService, UserJobExperience as UserJobExperienceEntity, UserJobExperienceService, } from "@cv/core"; 4 9 import { UseGuards } from "@nestjs/common"; 5 10 import { Args, Mutation, Resolver } from "@nestjs/graphql"; 6 11 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 7 - import { CompanyService } from "@cv/core"; 8 - import type { 9 - CreateUserJobExperienceDto, 10 - UpdateUserJobExperienceDto, 11 - } from "@cv/core"; 12 - import { UserJobExperience as UserJobExperienceEntity } from "@cv/core"; 13 - import { UserJobExperienceService } from "@cv/core"; 14 - import { LevelService } from "@cv/core"; 15 - import { RoleService } from "@cv/core"; 16 - import { SkillService } from "@cv/core"; 17 12 import { UserJobExperience } from "./user-job-experience.type"; 18 13 19 14 @Resolver(() => UserJobExperience)
+1 -2
apps/api/src/modules/job-experience/employment/graphql/user-field.resolver.ts
··· 1 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 - import { PaginationArgs, PaginationService, PrismaService } from "@cv/core"; 2 + import { PaginationArgs, PaginationService, PrismaService, UserJobExperienceService } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Args, Parent, ResolveField, Resolver } from "@nestjs/graphql"; 5 - import { UserJobExperienceService } from "@cv/core"; 6 5 import { User } from "@/modules/user/user.type"; 7 6 import { UserJobExperienceConnection } from "./user-job-experience.type"; 8 7
+5 -3
apps/api/src/modules/job-experience/employment/graphql/user-job-experience.type.ts
··· 1 + import { 2 + UserJobExperience as DomainUserJobExperience, 3 + Skill as SkillDomain, 4 + UserJobExperience as UserJobExperienceEntity, 5 + } from "@cv/core"; 1 6 import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 7 import { createConnection } from "@/modules/base/connection.factory"; 3 8 import { Company } from "@/modules/job-experience/company/graphql/company.type"; 4 - import type { UserJobExperience as DomainUserJobExperience } from "@cv/core"; 5 - import { UserJobExperience as UserJobExperienceEntity } from "@cv/core"; 6 9 import { Level } from "@/modules/job-experience/level/graphql/level.type"; 7 10 import { Role } from "@/modules/job-experience/role/graphql/role.type"; 8 11 import { Skill } from "@/modules/job-experience/skill/graphql/skill.type"; 9 - import type { Skill as SkillDomain } from "@cv/core"; 10 12 11 13 @ObjectType() 12 14 export class UserJobExperience {
+1 -2
apps/api/src/modules/job-experience/employment/onboarding/career-history.step.ts
··· 1 - import type { User } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 1 + import { PrismaService, User } from "@cv/core"; 3 2 import { Injectable } from "@nestjs/common"; 4 3 import { OnboardingStep } from "@/modules/onboarding/onboarding-step.decorator"; 5 4 import {
+3 -6
apps/api/src/modules/job-experience/level/graphql/level.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 - import { PaginationService } from "@cv/core"; 2 + import { User as DomainUser } from "@cv/core"; 3 + import { 4 + AuthorizationService, Level as LevelEntity, LevelFactory, LevelService, PaginationService, } from "@cv/core"; 5 5 import { UseGuards } from "@nestjs/common"; 6 6 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 7 7 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 8 - import { Level as LevelEntity } from "@cv/core"; 9 - import { LevelFactory } from "@cv/core"; 10 - import { LevelService } from "@cv/core"; 11 8 import { Level, LevelConnection } from "./level.type"; 12 9 import { LevelConnectionArgs } from "./level-connection-args.type"; 13 10
+1 -1
apps/api/src/modules/job-experience/level/graphql/level.type.ts
··· 1 - import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 2 1 import { Level as LevelDomain } from "@cv/core"; 2 + import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 3 3 4 4 const { Type, Connection, Edge } = createNamedGraphQLType("Level", LevelDomain); 5 5
+1 -3
apps/api/src/modules/job-experience/level/level.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "@cv/core"; 1 + import { BaseDataLoaderService, Level, LevelService } from "@cv/core"; 2 2 import { Injectable, Scope } from "@nestjs/common"; 3 - import { Level } from "@cv/core"; 4 - import { LevelService } from "@cv/core"; 5 3 6 4 @Injectable({ scope: Scope.REQUEST }) 7 5 export class LevelDataLoaderService extends BaseDataLoaderService<
+9 -6
apps/api/src/modules/job-experience/level/level.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + DatabaseModule, 5 + LevelFactory, 6 + LevelMapper, 7 + LevelPolicy, 8 + LevelService, 9 + } from "@cv/core"; 3 10 import { Module } from "@nestjs/common"; 4 11 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 - import { LevelService } from "@cv/core"; 6 12 import { LevelResolver } from "./graphql/level.resolver"; 7 13 import { LevelDataLoaderService } from "./level.dataloader"; 8 - import { LevelFactory } from "@cv/core"; 9 - import { LevelMapper } from "@cv/core"; 10 - import { LevelPolicy } from "@cv/core"; 11 14 12 15 @Module({ 13 16 imports: [
+3 -6
apps/api/src/modules/job-experience/role/graphql/role.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 - import { PaginationService } from "@cv/core"; 2 + import { User as DomainUser } from "@cv/core"; 3 + import { 4 + AuthorizationService, PaginationService, Role as RoleEntity, RoleFactory, RoleService, } from "@cv/core"; 5 5 import { UseGuards } from "@nestjs/common"; 6 6 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 7 7 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 8 - import { Role as RoleEntity } from "@cv/core"; 9 - import { RoleFactory } from "@cv/core"; 10 - import { RoleService } from "@cv/core"; 11 8 import { Role, RoleConnection } from "./role.type"; 12 9 import { RoleConnectionArgs } from "./role-connection-args.type"; 13 10
+1 -1
apps/api/src/modules/job-experience/role/graphql/role.type.ts
··· 1 - import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 2 1 import { Role as RoleDomain } from "@cv/core"; 2 + import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 3 3 4 4 const { Type, Connection, Edge } = createNamedGraphQLType("Role", RoleDomain); 5 5
+1 -3
apps/api/src/modules/job-experience/role/role.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "@cv/core"; 1 + import { BaseDataLoaderService, Role, RoleService } from "@cv/core"; 2 2 import { Injectable, Scope } from "@nestjs/common"; 3 - import { Role } from "@cv/core"; 4 - import { RoleService } from "@cv/core"; 5 3 6 4 @Injectable({ scope: Scope.REQUEST }) 7 5 export class RoleDataLoaderService extends BaseDataLoaderService<string, Role> {
+9 -6
apps/api/src/modules/job-experience/role/role.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + DatabaseModule, 5 + RoleFactory, 6 + RoleMapper, 7 + RolePolicy, 8 + RoleService, 9 + } from "@cv/core"; 3 10 import { Module } from "@nestjs/common"; 4 11 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 - import { RoleService } from "@cv/core"; 6 12 import { RoleResolver } from "./graphql/role.resolver"; 7 13 import { RoleDataLoaderService } from "./role.dataloader"; 8 - import { RoleFactory } from "@cv/core"; 9 - import { RoleMapper } from "@cv/core"; 10 - import { RolePolicy } from "@cv/core"; 11 14 12 15 @Module({ 13 16 imports: [
+9 -4
apps/api/src/modules/job-experience/seed/job-experience.seed.ts
··· 6 6 import { ReferenceDataSeedService } from "./reference-data.seed"; 7 7 8 8 @Injectable() 9 - @SeederDecorator({ name: "Job Experiences", dependsOn: ["Users", "Reference Data"] }) 9 + @SeederDecorator({ 10 + name: "Job Experiences", 11 + dependsOn: ["Users", "Reference Data"], 12 + }) 10 13 export class JobExperienceSeedService implements Seeder { 11 14 private readonly logger = new Logger(JobExperienceSeedService.name); 12 15 ··· 42 45 const existingProfile = await prisma.profile.findFirst({ 43 46 where: { userId: testUser.id }, 44 47 }); 45 - const profile = existingProfile ?? await prisma.profile.create({ 46 - data: { userId: testUser.id, name: testUser.name }, 47 - }); 48 + const profile = 49 + existingProfile ?? 50 + (await prisma.profile.create({ 51 + data: { userId: testUser.id, name: testUser.name }, 52 + })); 48 53 49 54 // Clear existing job experiences for this profile only 50 55 this.logger.log("Clearing existing job experiences for test user...");
+3 -1
apps/api/src/modules/job-experience/seed/reference-data.seed.ts
··· 165 165 166 166 return Promise.all( 167 167 names.map(async (name) => { 168 - const existing = await prisma.contractType.findFirst({ where: { name } }); 168 + const existing = await prisma.contractType.findFirst({ 169 + where: { name }, 170 + }); 169 171 if (existing) return { id: existing.id }; 170 172 const ct = await prisma.contractType.create({ data: { name } }); 171 173 return { id: ct.id };
+3 -6
apps/api/src/modules/job-experience/skill/graphql/skill.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 - import { PaginationService } from "@cv/core"; 2 + import { User as DomainUser } from "@cv/core"; 3 + import { 4 + AuthorizationService, PaginationService, Skill as SkillEntity, SkillFactory, SkillService, } from "@cv/core"; 5 5 import { UseGuards } from "@nestjs/common"; 6 6 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 7 7 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 8 - import { Skill as SkillEntity } from "@cv/core"; 9 - import { SkillFactory } from "@cv/core"; 10 - import { SkillService } from "@cv/core"; 11 8 import { Skill, SkillConnection } from "./skill.type"; 12 9 import { SkillConnectionArgs } from "./skill-connection-args.type"; 13 10
+1 -1
apps/api/src/modules/job-experience/skill/graphql/skill.type.ts
··· 1 - import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 2 1 import { Skill as SkillDomain } from "@cv/core"; 2 + import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 3 3 4 4 const { Type, Connection, Edge } = createNamedGraphQLType("Skill", SkillDomain); 5 5
+9 -6
apps/api/src/modules/job-experience/skill/skill.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + DatabaseModule, 5 + SkillFactory, 6 + SkillMapper, 7 + SkillPolicy, 8 + SkillService, 9 + } from "@cv/core"; 3 10 import { Module } from "@nestjs/common"; 4 11 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 - import { SkillService } from "@cv/core"; 6 12 import { JobExperienceSeedService } from "../seed/job-experience.seed"; 7 13 import { ReferenceDataSeedService } from "../seed/reference-data.seed"; 8 14 import { SkillResolver } from "./graphql/skill.resolver"; 9 - import { SkillFactory } from "@cv/core"; 10 - import { SkillMapper } from "@cv/core"; 11 - import { SkillPolicy } from "@cv/core"; 12 15 13 16 @Module({ 14 17 imports: [
+1 -1
apps/api/src/modules/messenger/logger.provider.ts
··· 1 1 import { Logger as NestLogger } from "@nestjs/common"; 2 - import type { Logger } from "@riotbyte-com/project-q-core"; 2 + import { Logger } from "@riotbyte-com/project-q-core"; 3 3 4 4 export class NestProjectQLogger implements Logger { 5 5 private readonly logger = new NestLogger("ProjectQ");
+4 -1
apps/api/src/modules/messenger/messenger.module.ts
··· 1 1 import { Module } from "@nestjs/common"; 2 - import { LoggerProvider, MessengerModule } from "@riotbyte-com/project-q-nestjs"; 2 + import { 3 + LoggerProvider, 4 + MessengerModule, 5 + } from "@riotbyte-com/project-q-nestjs"; 3 6 import { NestProjectQLogger } from "./logger.provider"; 4 7 import { PrismaTransportModule } from "./prisma-transport.module"; 5 8
+5 -2
apps/api/src/modules/messenger/prisma-transport.module.ts
··· 1 + import { DatabaseModule, PrismaService } from "@cv/core"; 1 2 import { Module } from "@nestjs/common"; 2 - import { DatabaseModule, PrismaService } from "@cv/core"; 3 - import { PrismaClientToken, PrismaTransportFactory } from "@riotbyte-com/project-q-prisma"; 3 + import { 4 + PrismaClientToken, 5 + PrismaTransportFactory, 6 + } from "@riotbyte-com/project-q-prisma"; 4 7 5 8 @Module({ 6 9 imports: [DatabaseModule],
+1 -1
apps/api/src/modules/onboarding/graphql/onboarding.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { User as DomainUser } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Mutation, Query, Resolver } from "@nestjs/graphql"; 5 5 import { CurrentUser } from "@/modules/current-user/current-user.decorator";
+1 -1
apps/api/src/modules/onboarding/onboarding-step.interface.ts
··· 1 - import type { User } from "@cv/core"; 1 + import { User } from "@cv/core"; 2 2 3 3 export enum OnboardingStepStatus { 4 4 NOT_STARTED = "NOT_STARTED",
+3 -4
apps/api/src/modules/onboarding/onboarding.service.ts
··· 1 - import type { User } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 3 - import { Injectable, Logger, OnModuleInit } from "@nestjs/common"; 1 + import { PrismaService, User } from "@cv/core"; 2 + import { Injectable, Logger, type OnModuleInit } from "@nestjs/common"; 4 3 import { DiscoveryService } from "@nestjs/core"; 5 4 import { ONBOARDING_STEP_METADATA_KEY } from "./onboarding-step.decorator"; 6 - import type { 5 + import { 7 6 OnboardingStepDefinition, 8 7 OnboardingStepStatus, 9 8 } from "./onboarding-step.interface";
+1 -2
apps/api/src/modules/organization/graphql/membership.type.ts
··· 1 - import { UserRole } from "@cv/core"; 1 + import { OrganizationRole, UserRole } from "@cv/core"; 2 2 import { Field, ObjectType } from "@nestjs/graphql"; 3 3 import { createConnection } from "@/modules/base/connection.factory"; 4 - import { OrganizationRole } from "@cv/core"; 5 4 import { User } from "@/modules/user/user.type"; 6 5 7 6 @ObjectType()
+3 -11
apps/api/src/modules/organization/graphql/organization.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService, UserService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 - import { PaginationService } from "@cv/core"; 2 + import { User as DomainUser, Membership } from "@cv/core"; 3 + import { 4 + AuthorizationService, MembershipSortField, MembershipSortOrder, Organization, OrganizationFactory, OrganizationService, PaginationService, UserService, } from "@cv/core"; 5 5 import { UseGuards } from "@nestjs/common"; 6 6 import { 7 7 Args, ··· 12 12 Resolver, 13 13 } from "@nestjs/graphql"; 14 14 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 15 - import { Organization } from "@cv/core"; 16 - import { OrganizationFactory } from "@cv/core"; 17 - import type { Membership } from "@cv/core"; 18 - import { 19 - type MembershipSortField, 20 - type MembershipSortOrder, 21 - OrganizationService, 22 - } from "@cv/core"; 23 15 import { MembershipConnection } from "./membership.type"; 24 16 import { MembershipConnectionArgs } from "./membership-connection-args.type"; 25 17
+1 -2
apps/api/src/modules/organization/graphql/user-field.resolver.ts
··· 1 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { Organization, OrganizationService } from "@cv/core"; 2 3 import { UseGuards } from "@nestjs/common"; 3 4 import { Parent, ResolveField, Resolver } from "@nestjs/graphql"; 4 - import { Organization } from "@cv/core"; 5 - import { OrganizationService } from "@cv/core"; 6 5 import { User } from "@/modules/user/user.type"; 7 6 8 7 @Resolver(() => User)
+12 -9
apps/api/src/modules/organization/organization.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + DatabaseModule, 5 + OrganizationFactory, 6 + OrganizationMapper, 7 + OrganizationPolicy, 8 + OrganizationRoleMapper, 9 + OrganizationRolePolicy, 10 + OrganizationRoleService, 11 + OrganizationService, 12 + } from "@cv/core"; 3 13 import { Module } from "@nestjs/common"; 4 14 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 15 import { OrganizationResolver } from "./graphql/organization.resolver"; 6 16 import { UserFieldResolver } from "./graphql/user-field.resolver"; 7 - import { OrganizationFactory } from "@cv/core"; 8 - import { OrganizationMapper } from "@cv/core"; 9 - import { OrganizationPolicy } from "@cv/core"; 10 - import { OrganizationService } from "@cv/core"; 11 - import { OrganizationRoleMapper } from "@cv/core"; 12 - import { OrganizationRolePolicy } from "@cv/core"; 13 - import { OrganizationRoleService } from "@cv/core"; 14 17 import { MembershipSeedService } from "./seed/membership.seed"; 15 18 import { OrganizationSeedService } from "./seed/organization.seed"; 16 19
+2 -8
apps/api/src/modules/profile/graphql/profile-field.resolver.ts
··· 1 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 - import { PaginationArgs, PaginationService } from "@cv/core"; 2 + import { CVService, EducationService, PaginationArgs, PaginationService, UserJobExperienceService } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Args, Parent, ResolveField, Resolver } from "@nestjs/graphql"; 5 - import { CVService } from "@cv/core"; 6 5 import { CVConnection } from "@/modules/cv-template/graphql/cv.type"; 7 - import { EducationService } from "@cv/core"; 8 6 import { EducationConnection } from "@/modules/education/graphql/education.type"; 9 - import { UserJobExperienceService } from "@cv/core"; 10 7 import { UserJobExperienceConnection } from "@/modules/job-experience/employment/graphql/user-job-experience.type"; 11 8 import { ProfileType } from "./profile.type"; 12 9 ··· 52 49 @Args() args: PaginationArgs = {}, 53 50 ): Promise<CVConnection> { 54 51 const options = this.paginationService.parsePaginationArgs(args); 55 - const result = await this.cvService.findManyForProfile( 56 - profile.id, 57 - options, 58 - ); 52 + const result = await this.cvService.findManyForProfile(profile.id, options); 59 53 return CVConnection.fromPaginationResult(result); 60 54 } 61 55 }
+2 -4
apps/api/src/modules/profile/graphql/profile.resolver.ts
··· 1 - import { type User as DomainUser, ProfileService } from "@cv/core"; 2 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { User as DomainUser, ProfileService } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Args, ID, Mutation, Query, Resolver } from "@nestjs/graphql"; 5 5 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; ··· 15 15 constructor(private readonly profileService: ProfileService) {} 16 16 17 17 @Query(() => [ProfileType]) 18 - async myProfiles( 19 - @CurrentUser() user: DomainUser, 20 - ): Promise<ProfileType[]> { 18 + async myProfiles(@CurrentUser() user: DomainUser): Promise<ProfileType[]> { 21 19 return this.profileService.getProfilesForUser(user.id); 22 20 } 23 21
+1 -2
apps/api/src/modules/profile/onboarding/profile.step.ts
··· 1 - import type { User } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 1 + import { PrismaService, User } from "@cv/core"; 3 2 import { Injectable } from "@nestjs/common"; 4 3 import { OnboardingStep } from "@/modules/onboarding/onboarding-step.decorator"; 5 4 import {
+2 -2
apps/api/src/modules/profile/profile.module.ts
··· 5 5 ProfilePolicy, 6 6 ProfileService, 7 7 } from "@cv/core"; 8 - import { Module, forwardRef } from "@nestjs/common"; 8 + import { forwardRef, Module } from "@nestjs/common"; 9 9 import { CVTemplateModule } from "@/modules/cv-template/cv-template.module"; 10 10 import { EducationModule } from "@/modules/education/education.module"; 11 11 import { EmploymentModule } from "@/modules/job-experience/employment/employment.module"; 12 - import { ProfileFieldResolver } from "./graphql/profile-field.resolver"; 13 12 import { ProfileResolver } from "./graphql/profile.resolver"; 13 + import { ProfileFieldResolver } from "./graphql/profile-field.resolver"; 14 14 import { ProfileOnboardingStep } from "./onboarding/profile.step"; 15 15 16 16 @Module({
+3 -4
apps/api/src/modules/user-settings/onboarding/ai-preference.step.ts
··· 1 - import type { User } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 1 + import { PrismaService, User } from "@cv/core"; 3 2 import { Injectable } from "@nestjs/common"; 4 3 import { OnboardingStep } from "@/modules/onboarding/onboarding-step.decorator"; 5 4 import { ··· 16 15 17 16 constructor(private readonly prisma: PrismaService) {} 18 17 19 - async computeStatus(user: User): Promise<OnboardingStepStatus> { 18 + async computeStatus({ id }: User): Promise<OnboardingStepStatus> { 20 19 const settings = await this.prisma.userAiSettings.findUnique({ 21 - where: { userId: user.id }, 20 + where: { userId: id }, 22 21 }); 23 22 24 23 return settings
+1 -1
apps/api/src/modules/user-settings/user-ai-settings.module.ts
··· 1 - import { AIModule, type AIProviderType } from "@cv/ai-provider"; 1 + import { AIModule, AIProviderType } from "@cv/ai-provider"; 2 2 import { DatabaseModule } from "@cv/core"; 3 3 import { Module } from "@nestjs/common"; 4 4 import { AiPreferenceOnboardingStep } from "./onboarding/ai-preference.step";
+2 -2
apps/api/src/modules/user-settings/user-ai-settings.resolver.ts
··· 1 - import type { AIProvider } from "@cv/ai-provider"; 1 + import { AIProvider } from "@cv/ai-provider"; 2 2 import { AI_PROVIDER, registeredProviderTypes } from "@cv/ai-provider"; 3 - import type { User as DomainUser } from "@cv/core"; 4 3 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 4 + import { User as DomainUser } from "@cv/core"; 5 5 import { Inject, Optional, UseGuards } from "@nestjs/common"; 6 6 import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; 7 7 import { AiPreference } from "@prisma/client";
+2 -4
apps/api/src/modules/user-settings/user-ai-settings.service.ts
··· 1 - import type { User } from "@cv/core"; 2 - import { TokenEncryptionService } from "@cv/core"; 3 - import { PrismaService } from "@cv/core"; 1 + import { PrismaService, TokenEncryptionService, User } from "@cv/core"; 4 2 import { 5 3 BadRequestException, 6 4 Injectable, 7 5 NotFoundException, 8 6 } from "@nestjs/common"; 9 - import type { AiPreference } from "@prisma/client"; 7 + import { AiPreference } from "@prisma/client"; 10 8 11 9 @Injectable() 12 10 export class UserAiSettingsService {
+1 -2
apps/api/src/modules/user/seed/user.seed.ts
··· 1 - import { CredentialsService, UserService } from "@cv/core"; 2 - import { PrismaService } from "@cv/core"; 1 + import { CredentialsService, PrismaService, UserService } from "@cv/core"; 3 2 import { Injectable, Logger } from "@nestjs/common"; 4 3 import { Seeder } from "@/modules/database/seed/seed.service"; 5 4 import { Seeder as SeederDecorator } from "@/modules/database/seed/seeder.decorator";
+3 -2
apps/api/src/modules/user/user.type.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 1 + import { User as DomainUser } from "@cv/core"; 2 2 import { UserRole } from "@cv/core"; 3 3 import { Field, ID, ObjectType, registerEnumType } from "@nestjs/graphql"; 4 4 5 5 registerEnumType(UserRole, { name: "UserRole" }); 6 + 7 + import { Organization } from "@cv/core"; 6 8 import { ApplicationConnection } from "@/modules/application/graphql/application.type"; 7 9 import { CVConnection } from "@/modules/cv-template/graphql/cv.type"; 8 10 import { EducationConnection } from "@/modules/education/graphql/education.type"; 9 11 import { UserJobExperienceConnection } from "@/modules/job-experience/employment/graphql/user-job-experience.type"; 10 - import { Organization } from "@cv/core"; 11 12 import { VacancyConnection } from "@/modules/vacancies/graphql/vacancy.type"; 12 13 13 14 @ObjectType()
+6 -4
apps/api/src/modules/vacancies/contract-type/contract-type.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "@cv/core"; 1 + import { 2 + BaseDataLoaderService, ContractType, ContractTypeService, } from "@cv/core"; 2 3 import { Injectable, Scope } from "@nestjs/common"; 3 - import { ContractType } from "@cv/core"; 4 - import { ContractTypeService } from "@cv/core"; 5 4 6 5 @Injectable({ scope: Scope.REQUEST }) 7 - export class ContractTypeDataLoaderService extends BaseDataLoaderService<string, ContractType> { 6 + export class ContractTypeDataLoaderService extends BaseDataLoaderService< 7 + string, 8 + ContractType 9 + > { 8 10 constructor(service: ContractTypeService) { 9 11 super(async (ids: readonly string[]) => { 10 12 const items = await service.findMany({ id: [...ids] });
+10 -4
apps/api/src/modules/vacancies/contract-type/contract-type.module.ts
··· 1 - import { DatabaseModule } from "@cv/core"; 1 + import { 2 + ContractTypeMapper, 3 + ContractTypeService, 4 + DatabaseModule, 5 + } from "@cv/core"; 2 6 import { Module } from "@nestjs/common"; 3 7 import { ContractTypeDataLoaderService } from "./contract-type.dataloader"; 4 - import { ContractTypeMapper } from "@cv/core"; 5 - import { ContractTypeService } from "@cv/core"; 6 8 7 9 @Module({ 8 10 imports: [DatabaseModule], 9 - providers: [ContractTypeService, ContractTypeMapper, ContractTypeDataLoaderService], 11 + providers: [ 12 + ContractTypeService, 13 + ContractTypeMapper, 14 + ContractTypeDataLoaderService, 15 + ], 10 16 exports: [ContractTypeService, ContractTypeDataLoaderService], 11 17 }) 12 18 export class ContractTypeModule {}
+5 -2
apps/api/src/modules/vacancies/contract-type/graphql/contract-type.type.ts
··· 1 - import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 2 1 import { ContractType as ContractTypeDomain } from "@cv/core"; 2 + import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 3 3 4 - const { Type, Connection, Edge } = createNamedGraphQLType("ContractType", ContractTypeDomain); 4 + const { Type, Connection, Edge } = createNamedGraphQLType( 5 + "ContractType", 6 + ContractTypeDomain, 7 + ); 5 8 6 9 export const ContractType = Type; 7 10 export type ContractType = InstanceType<typeof Type>;
+1 -2
apps/api/src/modules/vacancies/graphql/user-field.resolver.ts
··· 1 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 - import { PaginationArgs, PaginationService } from "@cv/core"; 2 + import { PaginationArgs, PaginationService, VacancyService } from "@cv/core"; 3 3 import { UseGuards } from "@nestjs/common"; 4 4 import { Args, Parent, ResolveField, Resolver } from "@nestjs/graphql"; 5 5 import { User } from "@/modules/user/user.type"; 6 - import { VacancyService } from "@cv/core"; 7 6 import { VacancyConnection } from "./vacancy.type"; 8 7 import { VacancyFilterInput } from "./vacancy-filter.input"; 9 8
+9 -10
apps/api/src/modules/vacancies/graphql/vacancy.resolver.ts
··· 1 - import type { User as DomainUser } from "@cv/core"; 2 - import { AuthorizationService } from "@cv/core"; 3 1 import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 2 + import { User as DomainUser } from "@cv/core"; 3 + import { 4 + AuthorizationService, SkillService, Vacancy as VacancyEntity, VacancyFactory, VacancyService, } from "@cv/core"; 5 + import { compact } from "@cv/utils"; 4 6 import { UseGuards } from "@nestjs/common"; 5 7 import { 6 8 Args, ··· 9 11 ResolveField, 10 12 Resolver, 11 13 } from "@nestjs/graphql"; 12 - import { compact } from "@cv/utils"; 13 14 import { CurrentUser } from "@/modules/current-user/current-user.decorator"; 14 15 import { CompanyDataLoaderService } from "@/modules/job-experience/company/company.dataloader"; 15 16 import { Company } from "@/modules/job-experience/company/graphql/company.type"; ··· 18 19 import { Role } from "@/modules/job-experience/role/graphql/role.type"; 19 20 import { RoleDataLoaderService } from "@/modules/job-experience/role/role.dataloader"; 20 21 import { Skill } from "@/modules/job-experience/skill/graphql/skill.type"; 21 - import { SkillService } from "@cv/core"; 22 22 import { ContractTypeDataLoaderService } from "@/modules/vacancies/contract-type/contract-type.dataloader"; 23 23 import { ContractType } from "@/modules/vacancies/contract-type/graphql/contract-type.type"; 24 - import { LocationDataLoaderService } from "@/modules/vacancies/location/location.dataloader"; 25 24 import { Location } from "@/modules/vacancies/location/graphql/location.type"; 26 - import { RateTypeDataLoaderService } from "@/modules/vacancies/rate-type/rate-type.dataloader"; 25 + import { LocationDataLoaderService } from "@/modules/vacancies/location/location.dataloader"; 27 26 import { RateType } from "@/modules/vacancies/rate-type/graphql/rate-type.type"; 28 - import { Vacancy as VacancyEntity } from "@cv/core"; 29 - import { VacancyFactory } from "@cv/core"; 30 - import { VacancyService } from "@cv/core"; 27 + import { RateTypeDataLoaderService } from "@/modules/vacancies/rate-type/rate-type.dataloader"; 31 28 import { Vacancy } from "./vacancy.type"; 32 29 33 30 @Resolver(() => Vacancy) ··· 162 159 @ResolveField(() => ContractType, { nullable: true }) 163 160 async contractType(@Parent() vacancy: Vacancy): Promise<ContractType | null> { 164 161 if (!vacancy.contractTypeId) return null; 165 - const contractType = await this.contractTypeDataLoader.load(vacancy.contractTypeId); 162 + const contractType = await this.contractTypeDataLoader.load( 163 + vacancy.contractTypeId, 164 + ); 166 165 return contractType ? ContractType.fromDomain(contractType) : null; 167 166 } 168 167
+4 -2
apps/api/src/modules/vacancies/graphql/vacancy.type.ts
··· 1 + import { 2 + Vacancy as DomainVacancy, 3 + Vacancy as VacancyEntity, 4 + } from "@cv/core"; 1 5 import { Field, ObjectType } from "@nestjs/graphql"; 2 6 import { BaseGraphQLType } from "@/modules/base/base-graphql-type"; 3 7 import { createConnection } from "@/modules/base/connection.factory"; 4 - import type { Vacancy as DomainVacancy } from "@cv/core"; 5 - import { Vacancy as VacancyEntity } from "@cv/core"; 6 8 7 9 @ObjectType() 8 10 export class Vacancy extends BaseGraphQLType {
+7 -4
apps/api/src/modules/vacancies/job-type/job-type.module.ts
··· 1 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + BaseModule, 3 + DatabaseModule, 4 + JobTypeFactory, 5 + JobTypeMapper, 6 + JobTypeService, 7 + } from "@cv/core"; 2 8 import { Module } from "@nestjs/common"; 3 - import { JobTypeFactory } from "@cv/core"; 4 - import { JobTypeMapper } from "@cv/core"; 5 - import { JobTypeService } from "@cv/core"; 6 9 7 10 @Module({ 8 11 imports: [DatabaseModule, BaseModule],
+1 -4
apps/api/src/modules/vacancies/location/graphql/location.type.ts
··· 1 + import { Location as LocationDomain, LocationType } from "@cv/core"; 1 2 import { Field, ID, ObjectType, registerEnumType } from "@nestjs/graphql"; 2 3 import { BaseGraphQLType } from "@/modules/base/base-graphql-type"; 3 - import { 4 - LocationType, 5 - type Location as LocationDomain, 6 - } from "@cv/core"; 7 4 8 5 registerEnumType(LocationType, { name: "LocationType" }); 9 6
+6 -4
apps/api/src/modules/vacancies/location/location.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "@cv/core"; 1 + import { 2 + BaseDataLoaderService, Location, VacancyLocationService, } from "@cv/core"; 2 3 import { Injectable, Scope } from "@nestjs/common"; 3 - import { Location } from "@cv/core"; 4 - import { VacancyLocationService } from "@cv/core"; 5 4 6 5 @Injectable({ scope: Scope.REQUEST }) 7 - export class LocationDataLoaderService extends BaseDataLoaderService<string, Location> { 6 + export class LocationDataLoaderService extends BaseDataLoaderService< 7 + string, 8 + Location 9 + > { 8 10 constructor(service: VacancyLocationService) { 9 11 super(async (ids: readonly string[]) => { 10 12 const items = await service.findMany({ id: [...ids] });
+10 -4
apps/api/src/modules/vacancies/location/location.module.ts
··· 1 - import { DatabaseModule } from "@cv/core"; 1 + import { 2 + DatabaseModule, 3 + LocationMapper, 4 + VacancyLocationService, 5 + } from "@cv/core"; 2 6 import { Module } from "@nestjs/common"; 3 7 import { LocationDataLoaderService } from "./location.dataloader"; 4 - import { LocationMapper } from "@cv/core"; 5 - import { VacancyLocationService } from "@cv/core"; 6 8 7 9 @Module({ 8 10 imports: [DatabaseModule], 9 - providers: [VacancyLocationService, LocationMapper, LocationDataLoaderService], 11 + providers: [ 12 + VacancyLocationService, 13 + LocationMapper, 14 + LocationDataLoaderService, 15 + ], 10 16 exports: [VacancyLocationService, LocationDataLoaderService], 11 17 }) 12 18 export class LocationModule {}
+5 -2
apps/api/src/modules/vacancies/rate-type/graphql/rate-type.type.ts
··· 1 - import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 2 1 import { RateType as RateTypeDomain } from "@cv/core"; 2 + import { createNamedGraphQLType } from "@/modules/base/named-graphql-type.factory"; 3 3 4 - const { Type, Connection, Edge } = createNamedGraphQLType("RateType", RateTypeDomain); 4 + const { Type, Connection, Edge } = createNamedGraphQLType( 5 + "RateType", 6 + RateTypeDomain, 7 + ); 5 8 6 9 export const RateType = Type; 7 10 export type RateType = InstanceType<typeof Type>;
+6 -4
apps/api/src/modules/vacancies/rate-type/rate-type.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "@cv/core"; 1 + import { 2 + BaseDataLoaderService, RateType, RateTypeService, } from "@cv/core"; 2 3 import { Injectable, Scope } from "@nestjs/common"; 3 - import { RateType } from "@cv/core"; 4 - import { RateTypeService } from "@cv/core"; 5 4 6 5 @Injectable({ scope: Scope.REQUEST }) 7 - export class RateTypeDataLoaderService extends BaseDataLoaderService<string, RateType> { 6 + export class RateTypeDataLoaderService extends BaseDataLoaderService< 7 + string, 8 + RateType 9 + > { 8 10 constructor(service: RateTypeService) { 9 11 super(async (ids: readonly string[]) => { 10 12 const items = await service.findMany({ id: [...ids] });
+1 -3
apps/api/src/modules/vacancies/rate-type/rate-type.module.ts
··· 1 - import { DatabaseModule } from "@cv/core"; 1 + import { DatabaseModule, RateTypeMapper, RateTypeService } from "@cv/core"; 2 2 import { Module } from "@nestjs/common"; 3 3 import { RateTypeDataLoaderService } from "./rate-type.dataloader"; 4 - import { RateTypeMapper } from "@cv/core"; 5 - import { RateTypeService } from "@cv/core"; 6 4 7 5 @Module({ 8 6 imports: [DatabaseModule],
+4 -1
apps/api/src/modules/vacancies/seed/vacancy.seed.ts
··· 5 5 import { Seeder as SeederDecorator } from "@/modules/database/seed/seeder.decorator"; 6 6 7 7 @Injectable() 8 - @SeederDecorator({ name: "Public Vacancies", dependsOn: ["Users", "Reference Data"] }) 8 + @SeederDecorator({ 9 + name: "Public Vacancies", 10 + dependsOn: ["Users", "Reference Data"], 11 + }) 9 12 export class VacancySeedService implements Seeder { 10 13 private readonly logger = new Logger(VacancySeedService.name); 11 14
+2 -3
apps/api/src/modules/vacancies/vacancy.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "@cv/core"; 1 + import { 2 + BaseDataLoaderService, Vacancy, VacancyService, } from "@cv/core"; 2 3 import { Injectable, Scope } from "@nestjs/common"; 3 - import { Vacancy } from "@cv/core"; 4 - import { VacancyService } from "@cv/core"; 5 4 6 5 @Injectable({ scope: Scope.REQUEST }) 7 6 export class VacancyDataLoaderService extends BaseDataLoaderService<
+9 -6
apps/api/src/modules/vacancies/vacancy.module.ts
··· 1 - import { AuthorizationModule } from "@cv/core"; 2 - import { BaseModule, DatabaseModule } from "@cv/core"; 1 + import { 2 + AuthorizationModule, 3 + BaseModule, 4 + DatabaseModule, 5 + VacancyFactory, 6 + VacancyMapper, 7 + VacancyPolicy, 8 + VacancyService, 9 + } from "@cv/core"; 3 10 import { Module } from "@nestjs/common"; 4 11 import { AuthenticationModule } from "@/modules/authentication/authentication.module"; 5 12 import { CompanyModule } from "@/modules/job-experience/company/company.module"; ··· 13 20 import { RateTypeModule } from "./rate-type/rate-type.module"; 14 21 import { VacancySeedService } from "./seed/vacancy.seed"; 15 22 import { VacancyDataLoaderService } from "./vacancy.dataloader"; 16 - import { VacancyFactory } from "@cv/core"; 17 - import { VacancyMapper } from "@cv/core"; 18 - import { VacancyPolicy } from "@cv/core"; 19 - import { VacancyService } from "@cv/core"; 20 23 21 24 @Module({ 22 25 imports: [
+1 -1
apps/api/test/app-integration.e2e-spec.ts
··· 1 - import type { INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { 3 3 BASIC_ME_QUERY, 4 4 HEALTH_QUERY,
+2 -2
apps/api/test/auth-integration.e2e-spec.ts
··· 1 - import type { INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { 3 3 BASIC_ME_QUERY, 4 4 COMPLEX_ME_QUERY, ··· 11 11 makeAuthenticatedRequest, 12 12 makeUnauthenticatedRequest, 13 13 setupTestApp, 14 - type TestUser, 14 + TestUser, 15 15 } from "./test-utils"; 16 16 17 17 describe("Auth Integration Tests (e2e)", () => {
+1 -1
apps/api/test/auth-refresh-token.e2e-spec.ts
··· 1 - import { type INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { Test } from "@nestjs/testing"; 3 3 import request from "supertest"; 4 4 import { AppModule } from "@/modules/app.module";
+2 -2
apps/api/test/auth.e2e-spec.ts
··· 1 - import type { INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { ConfigModule } from "@nestjs/config"; 3 - import { Test, type TestingModule } from "@nestjs/testing"; 3 + import { Test, TestingModule } from "@nestjs/testing"; 4 4 import request from "supertest"; 5 5 import { AppModule } from "../src/modules/app.module"; 6 6 import {
+2 -2
apps/api/test/cv-upload.e2e-spec.ts
··· 1 1 import { readFileSync } from "node:fs"; 2 2 import { join } from "node:path"; 3 - import type { INestApplication } from "@nestjs/common"; 3 + import { INestApplication } from "@nestjs/common"; 4 4 import { ConfigModule } from "@nestjs/config"; 5 - import { Test, type TestingModule } from "@nestjs/testing"; 5 + import { Test, TestingModule } from "@nestjs/testing"; 6 6 import request from "supertest"; 7 7 import { AppModule } from "../src/modules/app.module"; 8 8
+2 -2
apps/api/test/graphql-optimization.e2e-spec.ts
··· 1 - import type { INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { ConfigModule } from "@nestjs/config"; 3 - import { Test, type TestingModule } from "@nestjs/testing"; 3 + import { Test, TestingModule } from "@nestjs/testing"; 4 4 import request from "supertest"; 5 5 import { AppModule } from "../src/modules/app.module"; 6 6 import {
+2 -2
apps/api/test/job-experience-integration.e2e-spec.ts
··· 1 - import type { INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { 3 3 ALL_COMPANIES_QUERY, 4 4 ALL_LEVELS_QUERY, ··· 17 17 makeAuthenticatedRequest, 18 18 makeUnauthenticatedRequest, 19 19 setupTestApp, 20 - type TestUser, 20 + TestUser, 21 21 } from "./test-utils"; 22 22 23 23 describe("Job Experience Integration Tests (e2e)", () => {
+1 -1
apps/api/test/pagination-integration.e2e-spec.ts
··· 1 - import type { INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { 3 3 COMPANIES_PAGINATION_QUERY, 4 4 LEVELS_PAGINATION_QUERY,
+34 -25
apps/api/test/pdf-extraction.integration.ts
··· 1 1 import { readdirSync, readFileSync } from "node:fs"; 2 2 import { join } from "node:path"; 3 + import { PdfExtractionStrategy } from "@cv/file-upload"; 3 4 import { PDFExtractor } from "@cv/file-upload"; 4 - import type { PdfExtractionStrategy } from "@cv/file-upload"; 5 5 import { describe, expect, it } from "vitest"; 6 6 7 7 /** 8 8 * Lazy-import strategies to avoid pulling heavy deps at module level. 9 9 * Each factory returns a fresh strategy instance. 10 10 */ 11 - const strategyFactories: Record<string, () => Promise<PdfExtractionStrategy>> = { 12 - "pdf-parse": async () => { 13 - const { PdfParseStrategy } = await import( 14 - "@cv/file-upload/src/extractors/pdf/strategies/pdf-parse.strategy" 15 - ); 16 - return new PdfParseStrategy(); 17 - }, 18 - pdf2json: async () => { 19 - const { Pdf2JsonStrategy } = await import( 20 - "@cv/file-upload/src/extractors/pdf/strategies/pdf2json.strategy" 21 - ); 22 - return new Pdf2JsonStrategy(); 23 - }, 24 - "tesseract-ocr": async () => { 25 - const { TesseractOcrStrategy } = await import( 26 - "@cv/file-upload/src/extractors/pdf/strategies/tesseract-ocr.strategy" 27 - ); 28 - return new TesseractOcrStrategy(); 29 - }, 30 - }; 11 + const strategyFactories: Record<string, () => Promise<PdfExtractionStrategy>> = 12 + { 13 + "pdf-parse": async () => { 14 + const { PdfParseStrategy } = await import( 15 + "@cv/file-upload/src/extractors/pdf/strategies/pdf-parse.strategy" 16 + ); 17 + return new PdfParseStrategy(); 18 + }, 19 + pdf2json: async () => { 20 + const { Pdf2JsonStrategy } = await import( 21 + "@cv/file-upload/src/extractors/pdf/strategies/pdf2json.strategy" 22 + ); 23 + return new Pdf2JsonStrategy(); 24 + }, 25 + "tesseract-ocr": async () => { 26 + const { TesseractOcrStrategy } = await import( 27 + "@cv/file-upload/src/extractors/pdf/strategies/tesseract-ocr.strategy" 28 + ); 29 + return new TesseractOcrStrategy(); 30 + }, 31 + }; 31 32 32 33 const strategyNames = Object.keys(strategyFactories); 33 34 ··· 46 47 describe("composite extractor", () => { 47 48 const extractor = new PDFExtractor(); 48 49 49 - it.each(pdfFixtures)("extracts non-empty text from %s", async (filename) => { 50 + it.each( 51 + pdfFixtures, 52 + )("extracts non-empty text from %s", async (filename) => { 50 53 const buffer = readFileSync(join(ASSETS_DIR, filename)); 51 54 52 55 expect(buffer.slice(0, 5).toString()).toBe("%PDF-"); ··· 59 62 60 63 if (result.success) { 61 64 const trimmed = result.text.trim(); 62 - console.log(`[composite][${filename}] ${trimmed.length} chars extracted`); 63 - console.log(`[composite][${filename}] first 300 chars: ${trimmed.slice(0, 300)}`); 65 + console.log( 66 + `[composite][${filename}] ${trimmed.length} chars extracted`, 67 + ); 68 + console.log( 69 + `[composite][${filename}] first 300 chars: ${trimmed.slice(0, 300)}`, 70 + ); 64 71 expect(trimmed.length).toBeGreaterThan(50); 65 72 } else { 66 - console.log(`[composite][${filename}] extraction failed: ${result.error}`); 73 + console.log( 74 + `[composite][${filename}] extraction failed: ${result.error}`, 75 + ); 67 76 expect.fail(`Extraction failed for ${filename}: ${result.error}`); 68 77 } 69 78 });
+2 -2
apps/api/test/test-utils.ts
··· 1 - import type { INestApplication } from "@nestjs/common"; 1 + import { INestApplication } from "@nestjs/common"; 2 2 import { ConfigModule } from "@nestjs/config"; 3 - import { Test, type TestingModule } from "@nestjs/testing"; 3 + import { Test, TestingModule } from "@nestjs/testing"; 4 4 import request from "supertest"; 5 5 import { AppModule } from "../src/modules/app.module"; 6 6 import { REGISTER_MUTATION } from "./queries/load-queries";
+5 -1
apps/api/tsconfig.json
··· 25 25 "strictPropertyInitialization": true, 26 26 "noImplicitAny": true 27 27 }, 28 - "include": ["src/**/*.ts", "src/**/*.d.ts", "../../packages/tsconfig/graphql.d.ts"], 28 + "include": [ 29 + "src/**/*.ts", 30 + "src/**/*.d.ts", 31 + "../../packages/tsconfig/graphql.d.ts" 32 + ], 29 33 "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 30 34 }
+1 -3
apps/api/vitest.config.ts
··· 12 12 environment: "node", 13 13 testTimeout: 60000, 14 14 hookTimeout: 60000, 15 - include: [ 16 - "src/**/*.{test,spec}.{ts,tsx}", 17 - ], 15 + include: ["src/**/*.{test,spec}.{ts,tsx}"], 18 16 exclude: [ 19 17 "**/node_modules/**", 20 18 "**/dist/**",
+6 -1
apps/client/src/features/profile/components/ProfileSwitcher.tsx
··· 22 22 }; 23 23 24 24 return ( 25 - <div ref={containerRef} className="relative" role="listbox" onBlur={handleBlur}> 25 + <div 26 + ref={containerRef} 27 + className="relative" 28 + role="listbox" 29 + onBlur={handleBlur} 30 + > 26 31 <button 27 32 type="button" 28 33 onClick={() => setIsOpen((prev) => !prev)}
+4 -4
apps/client/src/types/graphql.d.ts
··· 1 1 declare module "*.graphql" { 2 - const content: string; 3 - export default content; 2 + const content: string; 3 + export default content; 4 4 } 5 5 6 6 declare module "*.gql" { 7 - const content: string; 8 - export default content; 7 + const content: string; 8 + export default content; 9 9 }
+1 -1
apps/worker/src/heartbeat/db-heartbeat.strategy.ts
··· 1 1 import pg from "pg"; 2 - import type { HeartbeatStrategy } from "./heartbeat.strategy"; 2 + import { HeartbeatStrategy } from "./heartbeat.strategy"; 3 3 4 4 /** Writes periodic heartbeats to a Postgres table for distributed monitoring. */ 5 5 export class DbHeartbeatStrategy implements HeartbeatStrategy {
+1 -1
apps/worker/src/heartbeat/file-heartbeat.strategy.ts
··· 1 1 import * as fs from "node:fs/promises"; 2 - import type { HeartbeatStrategy } from "./heartbeat.strategy"; 2 + import { HeartbeatStrategy } from "./heartbeat.strategy"; 3 3 4 4 /** Touches a file on each heartbeat tick for Docker/orchestrator health checks. */ 5 5 export class FileHeartbeatStrategy implements HeartbeatStrategy {
+1 -1
apps/worker/src/heartbeat/heartbeat.listener.ts
··· 5 5 WorkerStoppedEvent, 6 6 } from "@riotbyte-com/project-q-core"; 7 7 import { EventEmitter2 } from "eventemitter2"; 8 - import type { HeartbeatStrategy } from "./heartbeat.strategy"; 8 + import { HeartbeatStrategy } from "./heartbeat.strategy"; 9 9 import { HEARTBEAT_STRATEGIES } from "./heartbeat.strategy"; 10 10 11 11 @Injectable()
+3 -3
apps/worker/src/heartbeat/heartbeat.module.ts
··· 1 - import type { DynamicModule } from "@nestjs/common"; 1 + import { DynamicModule } from "@nestjs/common"; 2 2 import { Module } from "@nestjs/common"; 3 - import type { WorkerConfig } from "../config"; 3 + import { WorkerConfig } from "../config"; 4 4 import { DbHeartbeatStrategy } from "./db-heartbeat.strategy"; 5 5 import { FileHeartbeatStrategy } from "./file-heartbeat.strategy"; 6 6 import { HeartbeatListener } from "./heartbeat.listener"; 7 - import type { HeartbeatStrategy } from "./heartbeat.strategy"; 7 + import { HeartbeatStrategy } from "./heartbeat.strategy"; 8 8 import { HEARTBEAT_STRATEGIES } from "./heartbeat.strategy"; 9 9 10 10 @Module({})
+1 -1
apps/worker/src/logger.provider.ts
··· 1 1 import { Logger as NestLogger } from "@nestjs/common"; 2 - import type { Logger } from "@riotbyte-com/project-q-core"; 2 + import { Logger } from "@riotbyte-com/project-q-core"; 3 3 4 4 export class NestProjectQLogger implements Logger { 5 5 private readonly logger = new NestLogger("ProjectQ");
+1 -1
apps/worker/src/worker.module.ts
··· 16 16 PrismaClientToken, 17 17 PrismaTransportFactory, 18 18 } from "@riotbyte-com/project-q-prisma"; 19 - import type { WorkerConfig } from "./config"; 19 + import { WorkerConfig } from "./config"; 20 20 import { config } from "./config"; 21 21 import { HeartbeatModule } from "./heartbeat/heartbeat.module"; 22 22 import { NestProjectQLogger } from "./logger.provider";
+15 -3
biome.json
··· 35 35 "useConst": "error", 36 36 "useShorthandAssign": "error", 37 37 "useShorthandFunctionType": "error", 38 - "useCollapsedElseIf": "error" 38 + "useCollapsedElseIf": "error", 39 + "useImportType": "off" 39 40 }, 40 41 "suspicious": { 41 42 "recommended": true ··· 53 54 "indentWidth": 2, 54 55 "formatWithErrors": true 55 56 }, 57 + "assist": { 58 + "enabled": true, 59 + "actions": { 60 + "source": { 61 + "organizeImports": "on" 62 + } 63 + } 64 + }, 56 65 "javascript": { 57 66 "formatter": { 58 67 "jsxQuoteStyle": "double" ··· 76 85 }, 77 86 { 78 87 "includes": [ 79 - "apps/server/**/*", 88 + "apps/api/**/*", 80 89 "apps/worker/**/*", 81 90 "packages/auth/**/*", 82 - "packages/system/**/*", 91 + "packages/core/**/*", 92 + "packages/handlers/**/*", 93 + "packages/mail/**/*", 94 + "packages/file-storage/**/*", 83 95 "packages/ai-parser/**/*", 84 96 "packages/ai-provider/**/*", 85 97 "packages/file-upload/**/*"
+6 -5
packages/ai-parser/src/__tests__/ai-parser.service.spec.ts
··· 1 - import type { AIProvider, AICompletionResponse } from "@cv/ai-provider"; 2 - import type { Mocked } from "vitest"; 1 + import { AICompletionResponse, AIProvider } from "@cv/ai-provider"; 2 + import { Mocked } from "vitest"; 3 3 import { CVParserService } from "../ai-parser.service"; 4 - import type { ParsedCVData } from "../schemas"; 4 + import { ParsedCVData } from "../schemas"; 5 5 6 6 describe("CVParserService", () => { 7 7 let service: CVParserService; ··· 326 326 }), 327 327 ); 328 328 }); 329 - 330 329 }); 331 330 332 331 describe("prompt generation", () => { ··· 414 413 415 414 describe("error handling", () => { 416 415 it("propagates provider errors", async () => { 417 - mockProvider.complete.mockRejectedValue(new Error("Provider unavailable")); 416 + mockProvider.complete.mockRejectedValue( 417 + new Error("Provider unavailable"), 418 + ); 418 419 419 420 await expect(service.parseCVText("Sample CV")).rejects.toThrow( 420 421 "Provider unavailable",
+40 -39
packages/ai-parser/src/ai-parser.service.test.ts
··· 1 - import type { AIProvider, AICompletionResponse } from '@cv/ai-provider'; 2 - import { CVParserService } from './ai-parser.service'; 1 + import { AICompletionResponse, AIProvider } from "@cv/ai-provider"; 2 + import { CVParserService } from "./ai-parser.service"; 3 3 4 - describe('CVParserService Unit Tests', () => { 5 - const createMockProvider = ( 6 - mockResponse: string 7 - ): AIProvider => ({ 8 - name: 'mock', 9 - complete: vi.fn(async () => ({ 10 - content: mockResponse, 11 - model: 'mock-model', 12 - finishReason: 'stop', 13 - } as AICompletionResponse)), 4 + describe("CVParserService Unit Tests", () => { 5 + const createMockProvider = (mockResponse: string): AIProvider => ({ 6 + name: "mock", 7 + complete: vi.fn( 8 + async () => 9 + ({ 10 + content: mockResponse, 11 + model: "mock-model", 12 + finishReason: "stop", 13 + }) as AICompletionResponse, 14 + ), 14 15 isHealthy: vi.fn(async () => true), 15 16 }); 16 17 17 - describe('JSON Extraction', () => { 18 - it('should extract JSON from markdown code blocks', async () => { 18 + describe("JSON Extraction", () => { 19 + it("should extract JSON from markdown code blocks", async () => { 19 20 const mockProvider = createMockProvider(` 20 21 \`\`\`json 21 22 { ··· 30 31 `); 31 32 32 33 const parser = new CVParserService(mockProvider); 33 - const result = await parser.parseCVText('Sample CV text'); 34 + const result = await parser.parseCVText("Sample CV text"); 34 35 35 - expect(result.personalInfo.name).toBe('John Doe'); 36 + expect(result.personalInfo.name).toBe("John Doe"); 36 37 }); 37 38 38 - it('should extract raw JSON without markdown', async () => { 39 + it("should extract raw JSON without markdown", async () => { 39 40 const mockProvider = createMockProvider(` 40 41 { 41 42 "personalInfo": { ··· 48 49 `); 49 50 50 51 const parser = new CVParserService(mockProvider); 51 - const result = await parser.parseCVText('Sample CV text'); 52 + const result = await parser.parseCVText("Sample CV text"); 52 53 53 - expect(result.personalInfo.name).toBe('Jane Smith'); 54 + expect(result.personalInfo.name).toBe("Jane Smith"); 54 55 }); 55 56 }); 56 57 57 - describe('Error Handling', () => { 58 - it('should throw clear error for invalid JSON', async () => { 59 - const mockProvider = createMockProvider('This is not JSON'); 58 + describe("Error Handling", () => { 59 + it("should throw clear error for invalid JSON", async () => { 60 + const mockProvider = createMockProvider("This is not JSON"); 60 61 61 62 const parser = new CVParserService(mockProvider); 62 63 63 - await expect( 64 - parser.parseCVText('Sample CV') 65 - ).rejects.toThrow(/Failed to parse LLM response as JSON/); 64 + await expect(parser.parseCVText("Sample CV")).rejects.toThrow( 65 + /Failed to parse LLM response as JSON/, 66 + ); 66 67 }); 67 68 68 - it('should throw clear error for incomplete JSON', async () => { 69 + it("should throw clear error for incomplete JSON", async () => { 69 70 const mockProvider = createMockProvider('{"personalInfo": {'); 70 71 71 72 const parser = new CVParserService(mockProvider); 72 73 73 - await expect( 74 - parser.parseCVText('Sample CV') 75 - ).rejects.toThrow(/Failed to parse LLM response as JSON/); 74 + await expect(parser.parseCVText("Sample CV")).rejects.toThrow( 75 + /Failed to parse LLM response as JSON/, 76 + ); 76 77 }); 77 78 78 - it('should throw error for empty CV text', async () => { 79 - const mockProvider = createMockProvider('{}'); 79 + it("should throw error for empty CV text", async () => { 80 + const mockProvider = createMockProvider("{}"); 80 81 81 82 const parser = new CVParserService(mockProvider); 82 83 83 - await expect( 84 - parser.parseCVText('') 85 - ).rejects.toThrow(/CV text cannot be empty/); 84 + await expect(parser.parseCVText("")).rejects.toThrow( 85 + /CV text cannot be empty/, 86 + ); 86 87 }); 87 88 }); 88 89 89 - describe('Full CV Parsing', () => { 90 - it('should parse complete CV data', async () => { 90 + describe("Full CV Parsing", () => { 91 + it("should parse complete CV data", async () => { 91 92 const mockProvider = createMockProvider(` 92 93 { 93 94 "personalInfo": { ··· 120 121 `); 121 122 122 123 const parser = new CVParserService(mockProvider); 123 - const result = await parser.parseCVText('Sample CV text'); 124 + const result = await parser.parseCVText("Sample CV text"); 124 125 125 - expect(result.personalInfo.name).toBe('John Doe'); 126 + expect(result.personalInfo.name).toBe("John Doe"); 126 127 expect(result.jobExperiences).toHaveLength(1); 127 - expect(result.jobExperiences[0]!.companyName).toBe('Tech Corp'); 128 + expect(result.jobExperiences[0]!.companyName).toBe("Tech Corp"); 128 129 expect(result.jobExperiences[0]!.endDate).toBeNull(); 129 130 expect(result.education).toHaveLength(1); 130 131 expect(result.skills).toHaveLength(4);
+11 -11
packages/ai-parser/src/ai-parser.service.ts
··· 1 - import type { AIProvider } from '@cv/ai-provider'; 2 - import { ParsedCVDataSchema, type ParsedCVData } from './schemas'; 1 + import { AIProvider } from "@cv/ai-provider"; 3 2 import { 4 - CV_SYSTEM_PROMPT, 5 3 buildCvUserPrompt, 6 - type ExistingUserContext, 7 - } from './prompts'; 4 + CV_SYSTEM_PROMPT, 5 + ExistingUserContext, 6 + } from "./prompts"; 7 + import { ParsedCVData, ParsedCVDataSchema } from "./schemas"; 8 8 9 9 /** 10 10 * Configuration for CV parser service ··· 42 42 context?: ExistingUserContext, 43 43 ): Promise<ParsedCVData> { 44 44 if (!cvText || cvText.trim().length === 0) { 45 - throw new Error('CV text cannot be empty'); 45 + throw new Error("CV text cannot be empty"); 46 46 } 47 47 48 48 try { ··· 53 53 maxTokens: this.maxTokens, 54 54 }); 55 55 56 - if (response.finishReason === 'length') { 56 + if (response.finishReason === "length") { 57 57 throw new Error( 58 - 'LLM response was truncated (hit max token limit). ' + 59 - `Increase maxTokens (currently ${this.maxTokens}) to allow longer responses.` 58 + "LLM response was truncated (hit max token limit). " + 59 + `Increase maxTokens (currently ${this.maxTokens}) to allow longer responses.`, 60 60 ); 61 61 } 62 62 ··· 70 70 } catch (error) { 71 71 if (error instanceof SyntaxError) { 72 72 throw new Error( 73 - `Failed to parse LLM response as JSON: ${error.message}` 73 + `Failed to parse LLM response as JSON: ${error.message}`, 74 74 ); 75 75 } 76 76 77 - if (error instanceof Error && 'issues' in error) { 77 + if (error instanceof Error && "issues" in error) { 78 78 // Zod validation error 79 79 throw new Error(`CV data validation failed: ${error.message}`); 80 80 }
+2 -6
packages/ai-parser/src/cv-parser.module.ts
··· 1 - import { DynamicModule, Module } from "@nestjs/common"; 2 1 import { 3 - AI_PROVIDER, 4 - AIModule, 5 - type AIModuleOptions, 6 - type AIProvider, 7 - } from "@cv/ai-provider"; 2 + AI_PROVIDER, AIModule, AIModuleOptions, AIProvider, } from "@cv/ai-provider"; 3 + import { DynamicModule, Module } from "@nestjs/common"; 8 4 import { CVParserService } from "./ai-parser.service"; 9 5 10 6 export const CV_PARSER_SERVICE = Symbol("CV_PARSER_SERVICE");
+16 -18
packages/ai-parser/src/index.ts
··· 1 1 // Schemas and types 2 - export { 3 - ParsedCVDataSchema, 4 - ParsedJobExperienceSchema, 5 - ParsedEducationSchema, 6 - type ParsedCVData, 7 - type ParsedJobExperience, 8 - type ParsedEducation, 9 - } from './schemas'; 10 2 3 + // Service 4 + export { type CVParserConfig, CVParserService } from "./ai-parser.service"; 5 + // NestJS Module 6 + export { CV_PARSER_SERVICE, CVParserModule } from "./cv-parser.module"; 11 7 // Prompts 12 8 export { 13 - CV_SYSTEM_PROMPT, 14 - buildCvUserPrompt, 15 9 buildContextBlock, 10 + buildCvUserPrompt, 16 11 CV_PARSING_PROMPT, 17 - getCV_PARSING_PROMPT, 12 + CV_SYSTEM_PROMPT, 18 13 type ExistingUserContext, 19 - } from './prompts'; 20 - 21 - // Service 22 - export { CVParserService, type CVParserConfig } from './ai-parser.service'; 23 - 24 - // NestJS Module 25 - export { CVParserModule, CV_PARSER_SERVICE } from './cv-parser.module'; 14 + getCV_PARSING_PROMPT, 15 + } from "./prompts"; 16 + export { 17 + type ParsedCVData, 18 + ParsedCVDataSchema, 19 + type ParsedEducation, 20 + ParsedEducationSchema, 21 + type ParsedJobExperience, 22 + ParsedJobExperienceSchema, 23 + } from "./schemas";
+8 -2
packages/ai-parser/src/prompts.ts
··· 45 45 46 46 if (context.jobs?.length) { 47 47 const jobList = context.jobs 48 - .map((j) => ` - ${j.role} at ${j.company} (${formatRange(j.startDate, j.endDate)})`) 48 + .map( 49 + (j) => 50 + ` - ${j.role} at ${j.company} (${formatRange(j.startDate, j.endDate)})`, 51 + ) 49 52 .join("\n"); 50 53 lines.push(`Current jobs:\n${jobList}`); 51 54 } 52 55 53 56 if (context.education?.length) { 54 57 const eduList = context.education 55 - .map((e) => ` - ${e.degree} at ${e.institution} (${formatRange(e.startDate, e.endDate)})`) 58 + .map( 59 + (e) => 60 + ` - ${e.degree} at ${e.institution} (${formatRange(e.startDate, e.endDate)})`, 61 + ) 56 62 .join("\n"); 57 63 lines.push(`Education:\n${eduList}`); 58 64 }
+5 -5
packages/ai-parser/src/schemas.ts
··· 4 4 * Schema for parsed job experience extracted from CV text 5 5 */ 6 6 export const ParsedJobExperienceSchema = z.object({ 7 - companyName: z.string().min(1, 'Company name is required'), 8 - roleName: z.string().min(1, 'Role name is required'), 7 + companyName: z.string().min(1, "Company name is required"), 8 + roleName: z.string().min(1, "Role name is required"), 9 9 levelName: z 10 10 .string() 11 11 .nullish() 12 12 .transform((val) => val?.trim() || undefined), 13 - startDate: z.string().min(1, 'Start date is required'), // ISO date string YYYY-MM-DD 13 + startDate: z.string().min(1, "Start date is required"), // ISO date string YYYY-MM-DD 14 14 endDate: z.string().nullable().optional(), // ISO date string or null for current position 15 15 description: z 16 16 .string() ··· 25 25 * Schema for parsed education extracted from CV text 26 26 */ 27 27 export const ParsedEducationSchema = z.object({ 28 - institutionName: z.string().min(1, 'Institution name is required'), 28 + institutionName: z.string().min(1, "Institution name is required"), 29 29 degree: z 30 30 .string() 31 31 .nullish() ··· 36 36 .transform((val) => 37 37 Array.isArray(val) ? val.join(", ") : val?.trim() || undefined, 38 38 ), 39 - startDate: z.string().min(1, 'Start date is required'), // ISO date string YYYY-MM-DD 39 + startDate: z.string().min(1, "Start date is required"), // ISO date string YYYY-MM-DD 40 40 endDate: z.string().nullable().optional(), // ISO date string or null for currently studying 41 41 description: z 42 42 .string()
-70
packages/ai-provider/TEST_SETUP.md
··· 1 - # AI Provider Test Setup 2 - 3 - ## Overview 4 - 5 - This package has two types of tests: 6 - 7 - 1. **Unit Tests** (`.test.ts`) - Fast, mocked tests that don't require llama.cpp 8 - 2. **Integration Tests** (`.integration.test.ts`) - Slower tests that hit the actual llama.cpp service 9 - 10 - ## Running Tests 11 - 12 - ```bash 13 - # Run fast unit tests only (default) 14 - pnpm test 15 - 16 - # Run in watch mode during development 17 - pnpm test:watch 18 - 19 - # Run integration tests (requires llama.cpp running) 20 - pnpm test:integration 21 - 22 - # Run ALL tests (unit + integration) 23 - pnpm test:all 24 - ``` 25 - 26 - ## Integration Test Requirements 27 - 28 - Integration tests require: 29 - - Docker Compose services running (`docker compose up -d llama`) 30 - - llama.cpp healthy on `http://localhost:8080` 31 - - Model downloaded (~4.4GB Mistral 7B) 32 - 33 - **Note**: Integration tests are slow (~30-60 seconds) because they hit the real model. 34 - 35 - ## Test Structure 36 - 37 - ### Unit Tests (`ai-parser.service.test.ts`) 38 - - Mock the AI provider 39 - - Test JSON extraction logic 40 - - Test stop sequence configuration (caught the `'\n\n'` bug!) 41 - - Test error handling 42 - - **Fast**: Run in milliseconds 43 - 44 - ### Integration Tests (`llama-cpp.provider.integration.test.ts`) 45 - - Hit real llama.cpp service 46 - - Test health checks 47 - - Test basic completions 48 - - Test JSON generation 49 - - Test timeout handling 50 - - **Slow**: Run in 30+ seconds 51 - 52 - ## What These Tests Caught 53 - 54 - ✅ **Stop Sequence Bug**: Unit tests verify `'\n\n'` is NOT in stop sequences 55 - ✅ **Timeout Issues**: Integration tests verify timeouts work correctly 56 - ✅ **JSON Parsing**: Both test JSON extraction from various response formats 57 - ✅ **Error Handling**: Tests verify clear error messages for invalid responses 58 - 59 - ## CI/CD Recommendations 60 - 61 - In CI, run: 62 - ```bash 63 - # Fast feedback (unit tests only) 64 - pnpm test 65 - 66 - # Full validation (run integration tests separately, allow failures) 67 - pnpm test:integration || echo "Integration tests skipped or failed" 68 - ``` 69 - 70 - Integration tests can be flaky (model availability, performance), so don't block CI on them.
+6 -5
packages/ai-provider/src/ai-provider.registry.ts
··· 1 - import type { ConfigService } from '@nestjs/config'; 2 - import type { AIProvider } from './types'; 1 + import { ConfigService } from "@nestjs/config"; 2 + import { AIProvider } from "./types"; 3 3 4 4 type ProviderFactory = (configService: ConfigService) => AIProvider; 5 5 ··· 25 25 * Class decorator that auto-registers an AI provider. 26 26 * The decorated class must have a static `fromConfigService` method. 27 27 */ 28 - export const AIProviderRegistration = (type: string) => 28 + export const AIProviderRegistration = 29 + (type: string) => 29 30 <T extends AIProviderStatic>(target: T): T => { 30 31 registry.set(type, (cs) => target.fromConfigService(cs)); 31 32 return target; ··· 41 42 ): AIProvider => { 42 43 const factory = registry.get(type); 43 44 if (!factory) { 44 - const available = [...registry.keys()].join(', '); 45 + const available = [...registry.keys()].join(", "); 45 46 throw new Error( 46 - `Unknown AI provider type: "${type}". Registered providers: ${available || 'none'}`, 47 + `Unknown AI provider type: "${type}". Registered providers: ${available || "none"}`, 47 48 ); 48 49 } 49 50 return factory(configService);
+11 -8
packages/ai-provider/src/ai.module.ts
··· 1 - import { DynamicModule, Module } from '@nestjs/common'; 2 - import { ConfigModule, ConfigService } from '@nestjs/config'; 3 - import { resolveAIProvider } from './ai-provider.registry'; 4 - import type { AIProvider } from './types'; 1 + import { DynamicModule, Module } from "@nestjs/common"; 2 + import { ConfigModule, ConfigService } from "@nestjs/config"; 3 + import { resolveAIProvider } from "./ai-provider.registry"; 4 + import { AIProvider } from "./types"; 5 5 6 - import './providers'; 6 + import "./providers"; 7 7 8 - export const AI_PROVIDER = Symbol('AI_PROVIDER'); 8 + export const AI_PROVIDER = Symbol("AI_PROVIDER"); 9 9 10 - export type AIProviderType = 'llama-cpp' | 'openai' | 'anthropic'; 10 + export type AIProviderType = "llama-cpp" | "openai" | "anthropic"; 11 11 12 12 export interface AIModuleOptions { 13 13 type: AIProviderType; ··· 44 44 provide: AI_PROVIDER, 45 45 inject: [ConfigService], 46 46 useFactory: (configService: ConfigService): AIProvider => { 47 - const type = configService.get<AIProviderType>('AI_PROVIDER', 'llama-cpp'); 47 + const type = configService.get<AIProviderType>( 48 + "AI_PROVIDER", 49 + "llama-cpp", 50 + ); 48 51 return resolveAIProvider(type, configService); 49 52 }, 50 53 },
+31 -20
packages/ai-provider/src/index.ts
··· 1 1 // Types 2 + 3 + // NestJS Module 4 + export { 5 + AI_PROVIDER, 6 + AIModule, 7 + type AIModuleOptions, 8 + type AIProviderType, 9 + } from "./ai.module"; 10 + // Registry 11 + export { 12 + AIProviderRegistration, 13 + registerAIProvider, 14 + registeredProviderTypes, 15 + resolveAIProvider, 16 + } from "./ai-provider.registry"; 17 + 18 + // Providers 19 + export { 20 + type AnthropicConfig, 21 + AnthropicProvider, 22 + type LlamaCppConfig, 23 + LlamaCppProvider, 24 + type OpenAIConfig, 25 + OpenAIProvider, 26 + } from "./providers"; 27 + // Base class 28 + export { BaseAIProvider, type RequestOptions } from "./providers/base.provider"; 2 29 export type { 30 + AICompletionRequest, 31 + AICompletionResponse, 3 32 AIProvider, 4 33 AIProviderConfig, 5 34 AIProviderStatus, 6 35 AIProviderStatusDetails, 36 + ApiProviderStatusDetails, 7 37 LlamaCppStatusDetails, 8 - ApiProviderStatusDetails, 9 - AICompletionRequest, 10 - AICompletionResponse, 11 - } from './types'; 12 - 13 - // Base class 14 - export { BaseAIProvider, type RequestOptions } from './providers/base.provider'; 15 - 16 - // Providers 17 - export { 18 - LlamaCppProvider, type LlamaCppConfig, 19 - OpenAIProvider, type OpenAIConfig, 20 - AnthropicProvider, type AnthropicConfig, 21 - } from './providers'; 22 - 23 - // Registry 24 - export { registerAIProvider, AIProviderRegistration, resolveAIProvider, registeredProviderTypes } from './ai-provider.registry'; 25 - 26 - // NestJS Module 27 - export { AIModule, AI_PROVIDER, type AIModuleOptions, type AIProviderType } from './ai.module'; 38 + } from "./types";
+8 -10
packages/ai-provider/src/providers/anthropic.provider.ts
··· 1 - import type { ConfigService } from "@nestjs/config"; 1 + import { ConfigService } from "@nestjs/config"; 2 2 import { AIProviderRegistration } from "../ai-provider.registry"; 3 3 import { anthropicResponseSchema } from "../response-schemas"; 4 - import type { 4 + import { 5 5 AICompletionRequest, 6 6 AICompletionResponse, 7 7 AIProviderConfig, ··· 41 41 schema: anthropicResponseSchema, 42 42 }); 43 43 44 - const stopReasonMap: Record< 45 - string, 46 - AICompletionResponse["finishReason"] 47 - > = { 48 - end_turn: "stop", 49 - stop_sequence: "stop", 50 - max_tokens: "length", 51 - }; 44 + const stopReasonMap: Record<string, AICompletionResponse["finishReason"]> = 45 + { 46 + end_turn: "stop", 47 + stop_sequence: "stop", 48 + max_tokens: "length", 49 + }; 52 50 53 51 const textBlock = result.content?.find(({ type }) => type === "text"); 54 52
+2 -8
packages/ai-provider/src/providers/base.provider.ts
··· 1 - import type { z } from "zod/v4"; 2 - import type { 3 - AICompletionRequest, 4 - AICompletionResponse, 5 - AIProvider, 6 - AIProviderConfig, 7 - AIProviderStatus, 8 - } from "../types"; 1 + import { z } from "zod/v4"; 2 + import { AICompletionRequest, AICompletionResponse, AIProvider, AIProviderConfig, AIProviderStatus } from "../types"; 9 3 10 4 /** 11 5 * Options for the `request()` helper.
+4 -4
packages/ai-provider/src/providers/index.ts
··· 1 - export { BaseAIProvider, type RequestOptions } from './base.provider'; 2 - export { LlamaCppProvider, type LlamaCppConfig } from './llama-cpp.provider'; 3 - export { OpenAIProvider, type OpenAIConfig } from './openai.provider'; 4 - export { AnthropicProvider, type AnthropicConfig } from './anthropic.provider'; 1 + export { type AnthropicConfig, AnthropicProvider } from "./anthropic.provider"; 2 + export { BaseAIProvider, type RequestOptions } from "./base.provider"; 3 + export { type LlamaCppConfig, LlamaCppProvider } from "./llama-cpp.provider"; 4 + export { type OpenAIConfig, OpenAIProvider } from "./openai.provider";
+24 -23
packages/ai-provider/src/providers/llama-cpp.provider.integration.test.ts
··· 1 - import { describe, it, expect, beforeAll } from 'vitest'; 2 - import { LlamaCppProvider } from './llama-cpp.provider'; 1 + import { beforeAll, describe, expect, it } from "vitest"; 2 + import { LlamaCppProvider } from "./llama-cpp.provider"; 3 3 4 - describe('LlamaCppProvider Integration Tests', () => { 4 + describe("LlamaCppProvider Integration Tests", () => { 5 5 let provider: LlamaCppProvider; 6 6 7 7 beforeAll(() => { 8 8 provider = new LlamaCppProvider({ 9 - baseUrl: process.env['LLAMA_URL'] || 'http://localhost:8080', 9 + baseUrl: process.env["LLAMA_URL"] || "http://localhost:8080", 10 10 defaultTemperature: 0.1, 11 11 defaultMaxTokens: 100, 12 12 timeout: 10000, // 10 second timeout for tests 13 13 }); 14 14 }); 15 15 16 - describe('Health Check', () => { 17 - it('should report healthy when llama.cpp is running', async () => { 16 + describe("Health Check", () => { 17 + it("should report healthy when llama.cpp is running", async () => { 18 18 const isHealthy = await provider.isHealthy(); 19 19 expect(isHealthy).toBe(true); 20 20 }, 15000); 21 21 }); 22 22 23 - describe('Basic Completion', () => { 24 - it('should generate a simple completion', async () => { 23 + describe("Basic Completion", () => { 24 + it("should generate a simple completion", async () => { 25 25 const response = await provider.complete({ 26 26 prompt: 'Say "Hello World" and nothing else.', 27 27 temperature: 0.1, 28 28 maxTokens: 50, 29 - stopSequences: ['</s>'], 29 + stopSequences: ["</s>"], 30 30 }); 31 31 32 32 expect(response.content).toBeTruthy(); ··· 34 34 expect(response.finishReason).toBeDefined(); 35 35 }, 30000); 36 36 37 - it('should handle stop sequences correctly', async () => { 37 + it("should handle stop sequences correctly", async () => { 38 38 const response = await provider.complete({ 39 - prompt: 'Count: 1, 2, 3', 39 + prompt: "Count: 1, 2, 3", 40 40 temperature: 0.1, 41 41 maxTokens: 100, 42 - stopSequences: ['</s>'], // Should NOT include '\n\n' as that causes early termination 42 + stopSequences: ["</s>"], // Should NOT include '\n\n' as that causes early termination 43 43 }); 44 44 45 45 expect(response.content).toBeTruthy(); ··· 47 47 }, 30000); 48 48 }); 49 49 50 - describe('JSON Generation', () => { 51 - it('should generate valid JSON', async () => { 50 + describe("JSON Generation", () => { 51 + it("should generate valid JSON", async () => { 52 52 const response = await provider.complete({ 53 53 prompt: 'Return ONLY this JSON object: {"name": "Test", "value": 123}', 54 54 temperature: 0.1, 55 55 maxTokens: 100, 56 - stopSequences: ['</s>'], 56 + stopSequences: ["</s>"], 57 57 }); 58 58 59 59 expect(response.content).toBeTruthy(); ··· 64 64 65 65 if (jsonMatch) { 66 66 const parsed = JSON.parse(jsonMatch[0]); 67 - expect(parsed).toHaveProperty('name'); 67 + expect(parsed).toHaveProperty("name"); 68 68 } 69 69 }, 30000); 70 70 }); 71 71 72 - describe('Error Handling', () => { 73 - it('should timeout for requests exceeding configured timeout', async () => { 72 + describe("Error Handling", () => { 73 + it("should timeout for requests exceeding configured timeout", async () => { 74 74 const slowProvider = new LlamaCppProvider({ 75 - baseUrl: process.env['LLAMA_URL'] || 'http://localhost:8080', 75 + baseUrl: process.env["LLAMA_URL"] || "http://localhost:8080", 76 76 timeout: 1000, // 1 second timeout 77 77 }); 78 78 79 79 await expect( 80 80 slowProvider.complete({ 81 - prompt: 'Write a very long essay about the history of computing. Include detailed information about every decade from the 1940s to present day.', 81 + prompt: 82 + "Write a very long essay about the history of computing. Include detailed information about every decade from the 1940s to present day.", 82 83 maxTokens: 2000, 83 - }) 84 + }), 84 85 ).rejects.toThrow(); 85 86 }, 5000); 86 87 87 - it('should handle invalid base URL gracefully', async () => { 88 + it("should handle invalid base URL gracefully", async () => { 88 89 const badProvider = new LlamaCppProvider({ 89 - baseUrl: 'http://localhost:9999', // Invalid port 90 + baseUrl: "http://localhost:9999", // Invalid port 90 91 timeout: 2000, 91 92 }); 92 93
+2 -2
packages/ai-provider/src/providers/llama-cpp.provider.ts
··· 1 - import type { ConfigService } from "@nestjs/config"; 1 + import { ConfigService } from "@nestjs/config"; 2 2 import { AIProviderRegistration } from "../ai-provider.registry"; 3 3 import { openaiResponseSchema } from "../response-schemas"; 4 - import type { 4 + import { 5 5 AICompletionRequest, 6 6 AICompletionResponse, 7 7 AIProviderConfig,
+2 -2
packages/ai-provider/src/providers/openai.provider.ts
··· 1 - import type { ConfigService } from "@nestjs/config"; 1 + import { ConfigService } from "@nestjs/config"; 2 2 import { AIProviderRegistration } from "../ai-provider.registry"; 3 3 import { openaiResponseSchema } from "../response-schemas"; 4 - import type { 4 + import { 5 5 AICompletionRequest, 6 6 AICompletionResponse, 7 7 AIProviderConfig,
+3 -3
packages/ai-provider/src/types.ts
··· 27 27 /** Model used for completion */ 28 28 model?: string | undefined; 29 29 /** Whether generation was cut off */ 30 - finishReason?: 'stop' | 'length' | 'content_filter' | 'error' | undefined; 30 + finishReason?: "stop" | "length" | "content_filter" | "error" | undefined; 31 31 } 32 32 33 33 /** ··· 52 52 * Status details for a llama.cpp local provider 53 53 */ 54 54 export interface LlamaCppStatusDetails { 55 - kind: 'llama-cpp'; 55 + kind: "llama-cpp"; 56 56 health: Record<string, unknown> | null; 57 57 model: Record<string, unknown> | null; 58 58 slots: Array<Record<string, unknown>> | null; ··· 63 63 * Status details for a cloud API provider (Anthropic, OpenAI, etc.) 64 64 */ 65 65 export interface ApiProviderStatusDetails { 66 - kind: 'api'; 66 + kind: "api"; 67 67 model: string; 68 68 baseUrl: string; 69 69 }
+2 -2
packages/auth/src/auth.module.ts
··· 1 - import { Global, Module } from "@nestjs/common"; 2 - import { DiscoveryModule } from "@nestjs/core"; 3 1 import { 4 2 AuthorizationModule, 5 3 IdentityProviderRegistry, 6 4 TokenModule, 7 5 UserModule, 8 6 } from "@cv/core"; 7 + import { Global, Module } from "@nestjs/common"; 8 + import { DiscoveryModule } from "@nestjs/core"; 9 9 import { JwtAuthGuard } from "./guards/jwt-auth.guard"; 10 10 import { VerifiedScopeGuard } from "./guards/verified-scope.guard"; 11 11
+1 -1
packages/auth/src/guards/admin.guard.ts
··· 1 + import { User } from "@cv/core"; 1 2 import { 2 3 type CanActivate, 3 4 type ExecutionContext, ··· 5 6 Injectable, 6 7 } from "@nestjs/common"; 7 8 import { GqlExecutionContext } from "@nestjs/graphql"; 8 - import type { User } from "@cv/core"; 9 9 10 10 /** 11 11 * Guard that restricts access to admin users.
+6 -6
packages/auth/src/guards/jwt-auth.guard.ts
··· 1 + import { 2 + EntityNotFoundError, 3 + InvalidTokenError, 4 + NoTokenError, 5 + UserService, 6 + } from "@cv/core"; 1 7 import { 2 8 type CanActivate, 3 9 type ExecutionContext, ··· 5 11 } from "@nestjs/common"; 6 12 import { GqlExecutionContext } from "@nestjs/graphql"; 7 13 import { JwtService } from "@nestjs/jwt"; 8 - import { 9 - EntityNotFoundError, 10 - InvalidTokenError, 11 - NoTokenError, 12 - UserService, 13 - } from "@cv/core"; 14 14 15 15 @Injectable() 16 16 export class JwtAuthGuard implements CanActivate {
+1 -1
packages/auth/src/guards/verified-scope.guard.ts
··· 1 + import { JwtScope } from "@cv/core"; 1 2 import { 2 3 type CanActivate, 3 4 type ExecutionContext, ··· 6 7 } from "@nestjs/common"; 7 8 import { GqlExecutionContext } from "@nestjs/graphql"; 8 9 import { JwtService } from "@nestjs/jwt"; 9 - import { JwtScope } from "@cv/core"; 10 10 11 11 @Injectable() 12 12 export class VerifiedScopeGuard implements CanActivate {
+6 -6
packages/biome-config/package.json
··· 1 1 { 2 - "name": "@cv/biome-config", 3 - "version": "0.0.0", 4 - "private": true, 5 - "files": [ 6 - "biome.json" 7 - ] 2 + "name": "@cv/biome-config", 3 + "version": "0.0.0", 4 + "private": true, 5 + "files": [ 6 + "biome.json" 7 + ] 8 8 }
+1 -1
packages/core/prisma.config.ts
··· 1 1 import path from "node:path"; 2 - import type { PrismaConfig } from "prisma"; 2 + import { PrismaConfig } from "prisma"; 3 3 4 4 export default { 5 5 schema: path.join(__dirname, "prisma"),
+4 -4
packages/core/src/index.ts
··· 1 - export * from "./shared"; 2 - export * from "./modules/database"; 1 + export * from "./modules/application"; 3 2 export * from "./modules/auth"; 4 - export * from "./modules/events"; 5 - export * from "./modules/application"; 6 3 export * from "./modules/authentication"; 7 4 export * from "./modules/cv-template"; 5 + export * from "./modules/database"; 8 6 export * from "./modules/education"; 7 + export * from "./modules/events"; 9 8 export * from "./modules/job-experience/company"; 10 9 export * from "./modules/job-experience/employment"; 11 10 export * from "./modules/job-experience/level"; ··· 14 13 export * from "./modules/organization"; 15 14 export * from "./modules/profile"; 16 15 export * from "./modules/vacancies"; 16 + export * from "./shared";
+2 -2
packages/core/src/modules/application/application-status/application-status.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { CreateApplicationStatusDto } from "./application-status.dto"; 2 + import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 3 + import { CreateApplicationStatusDto } from "./application-status.dto"; 4 4 import { ApplicationStatus } from "./application-status.entity"; 5 5 6 6 @Injectable()
+3 -5
packages/core/src/modules/application/application-status/application-status.mapper.ts
··· 1 - import { createNamedEntityMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { ApplicationStatus as PrismaApplicationStatus } from "@prisma/client"; 2 + import { ApplicationStatus as PrismaApplicationStatus } from "@prisma/client"; 3 + import { createNamedEntityMapper } from "../../../shared"; 4 4 import { ApplicationStatus } from "./application-status.entity"; 5 5 6 6 @Injectable() ··· 21 21 return this.mapper.toDomain(prismaEntity); 22 22 } 23 23 24 - mapToDomain( 25 - prismaEntities: PrismaApplicationStatus[], 26 - ): ApplicationStatus[] { 24 + mapToDomain(prismaEntities: PrismaApplicationStatus[]): ApplicationStatus[] { 27 25 return this.mapper.mapToDomain(prismaEntities); 28 26 } 29 27 }
+1 -1
packages/core/src/modules/application/application-status/application-status.module.ts
··· 1 + import { Module } from "@nestjs/common"; 1 2 import { BaseModule } from "../../../shared"; 2 3 import { DatabaseModule } from "../../database"; 3 - import { Module } from "@nestjs/common"; 4 4 import { ApplicationStatusFactory } from "./application-status.factory"; 5 5 import { ApplicationStatusMapper } from "./application-status.mapper"; 6 6 import { ApplicationStatusService } from "./application-status.service";
+1 -1
packages/core/src/modules/application/application-status/application-status.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { NamedEntityService } from "../../../shared"; 2 3 import { PrismaService } from "../../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { ApplicationStatus } from "./application-status.entity"; 5 5 import { ApplicationStatusMapper } from "./application-status.mapper"; 6 6
+2 -2
packages/core/src/modules/application/application.mapper.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 - import type { Prisma } from "@prisma/client"; 2 + import { Prisma } from "@prisma/client"; 3 3 import { cvMapper } from "../cv-template/cv.mapper"; 4 4 import { VacancyMapper } from "../vacancies/vacancy.mapper"; 5 5 import { 6 6 Application, 7 - type ApplicationStatusRelation, 7 + ApplicationStatusRelation, 8 8 } from "./application.entity"; 9 9 10 10 type PrismaApplicationWithRelations = Prisma.ApplicationGetPayload<{
+3 -7
packages/core/src/modules/application/application.module.ts
··· 1 - import { AuthorizationModule } from "../auth"; 2 - import { DatabaseModule } from "../database"; 3 1 import { Module } from "@nestjs/common"; 2 + import { AuthorizationModule } from "../auth"; 4 3 import { AuthenticationModule } from "../authentication/authentication.module"; 5 4 import { CVTemplateModule } from "../cv-template/cv-template.module"; 5 + import { DatabaseModule } from "../database"; 6 6 import { VacancyModule } from "../vacancies/vacancy.module"; 7 7 import { ApplicationMapper } from "./application.mapper"; 8 8 import { ApplicationPolicy } from "./application.policy"; ··· 16 16 VacancyModule, 17 17 CVTemplateModule, 18 18 ], 19 - providers: [ 20 - ApplicationService, 21 - ApplicationMapper, 22 - ApplicationPolicy, 23 - ], 19 + providers: [ApplicationService, ApplicationMapper, ApplicationPolicy], 24 20 exports: [ApplicationService, ApplicationMapper], 25 21 }) 26 22 export class ApplicationModule {}
+1 -1
packages/core/src/modules/application/application.policy.ts
··· 1 - import { Policy, UserOwnedResourcePolicy } from "../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, UserOwnedResourcePolicy } from "../auth"; 3 3 import { Application } from "./application.entity"; 4 4 5 5 @Injectable()
+3 -3
packages/core/src/modules/application/application.service.ts
··· 1 - import { notFound } from "../auth"; 2 - import { type EntityService } from "../../shared"; 3 - import { PrismaService } from "../database"; 4 1 import { Injectable } from "@nestjs/common"; 5 2 import { Prisma } from "@prisma/client"; 3 + import { EntityService } from "../../shared"; 4 + import { notFound } from "../auth"; 5 + import { PrismaService } from "../database"; 6 6 import { Application } from "./application.entity"; 7 7 import { DuplicateApplicationError } from "./application.error"; 8 8 import { ApplicationMapper } from "./application.mapper";
+1 -1
packages/core/src/modules/application/index.ts
··· 7 7 export { ApplicationModule } from "./application.module"; 8 8 export { ApplicationPolicy } from "./application.policy"; 9 9 export { ApplicationService } from "./application.service"; 10 - export { ApplicationStatus } from "./application-status/application-status.entity"; 11 10 export type { 12 11 CreateApplicationStatusDto, 13 12 UpdateApplicationStatusDto, 14 13 } from "./application-status/application-status.dto"; 14 + export { ApplicationStatus } from "./application-status/application-status.entity"; 15 15 export { ApplicationStatusFactory } from "./application-status/application-status.factory"; 16 16 export { ApplicationStatusMapper } from "./application-status/application-status.mapper"; 17 17 export { ApplicationStatusModule } from "./application-status/application-status.module";
+2 -2
packages/core/src/modules/auth/authorization/authorization.module.ts
··· 1 1 import { Module } from "@nestjs/common"; 2 - import { DiscoveryModule } from "@nestjs/core"; 2 + import { ServiceLocatorModule } from "@riotbyte-com/nest-service-locator"; 3 3 import { AuthorizationService } from "./authorization.service"; 4 4 import { PolicyRegistry } from "./policy-registry.service"; 5 5 6 6 @Module({ 7 - imports: [DiscoveryModule], 7 + imports: [ServiceLocatorModule], 8 8 providers: [PolicyRegistry, AuthorizationService], 9 9 exports: [AuthorizationService, PolicyRegistry], 10 10 })
+2 -2
packages/core/src/modules/auth/authorization/authorization.service.ts
··· 1 - import type { BaseEntity } from "../../../shared"; 2 1 import { Injectable, type Type } from "@nestjs/common"; 2 + import { BaseEntity } from "../../../shared"; 3 3 import { 4 4 CannotCreateError, 5 5 CannotDeleteError, 6 6 CannotUpdateError, 7 7 CannotViewError, 8 8 } from "../errors/authorization.error"; 9 - import type { User } from "../user/user.entity"; 9 + import { User } from "../user/user.entity"; 10 10 import { PolicyRegistry } from "./policy-registry.service"; 11 11 12 12 @Injectable()
+1 -1
packages/core/src/modules/auth/authorization/index.ts
··· 1 1 export * from "./authorization.module"; 2 2 export * from "./authorization.service"; 3 3 export * from "./owner-owned-resource.policy"; 4 - export { Policy, POLICY_RESOURCE_KEY } from "./policy.decorator"; 4 + export { POLICY_RESOURCE_KEY, Policy } from "./policy.decorator"; 5 5 export type { Policy as IPolicy } from "./policy.interface"; 6 6 export * from "./policy-registry.service"; 7 7 export * from "./profile-owned-resource.policy";
+2 -2
packages/core/src/modules/auth/authorization/owner-owned-resource.policy.ts
··· 1 - import type { User } from "../user/user.entity"; 2 - import type { Policy } from "./policy.interface"; 1 + import { User } from "../user/user.entity"; 2 + import { Policy } from "./policy.interface"; 3 3 4 4 export abstract class OwnerOwnedResourcePolicy< 5 5 TResource extends { ownerId: string },
+13 -59
packages/core/src/modules/auth/authorization/policy-registry.service.ts
··· 1 + import { Injectable, type Type } from "@nestjs/common"; 2 + import { ServiceLocator } from "@riotbyte-com/nest-service-locator"; 1 3 import { raise } from "../../../shared"; 2 - import { Injectable, type OnModuleInit, type Type } from "@nestjs/common"; 3 - import { DiscoveryService, Reflector } from "@nestjs/core"; 4 - import { POLICY_RESOURCE_KEY } from "./policy.decorator"; 5 - import type { Policy } from "./policy.interface"; 4 + import { Policy } from "./policy.interface"; 5 + import { PolicyTag } from "./policy-tag"; 6 6 7 7 @Injectable() 8 - export class PolicyRegistry implements OnModuleInit { 9 - private readonly policies = new Map<Type<unknown>, Policy<unknown>>(); 10 - 11 - constructor( 12 - private readonly discoveryService: DiscoveryService, 13 - private readonly reflector: Reflector, 14 - ) {} 15 - 16 - onModuleInit(): void { 17 - const providers = this.discoveryService.getProviders(); 18 - 19 - for (const provider of providers) { 20 - const { instance, metatype } = provider; 21 - 22 - if (!instance) { 23 - continue; 24 - } 25 - 26 - if (!metatype) { 27 - continue; 28 - } 29 - 30 - const resourceType = this.reflector.get<Type<unknown>>( 31 - POLICY_RESOURCE_KEY, 32 - metatype, 33 - ); 34 - 35 - if (!resourceType) { 36 - continue; 37 - } 38 - 39 - const policy = instance as Policy<unknown>; 40 - 41 - if (this.isPolicy(policy)) { 42 - this.policies.set(resourceType, policy); 43 - } 44 - } 45 - } 8 + export class PolicyRegistry { 9 + constructor(private readonly locator: ServiceLocator) {} 46 10 47 11 getPolicy<TResource>(resourceType: Type<TResource>): Policy<TResource> { 12 + const found = this.locator 13 + .tagged(PolicyTag) 14 + .find((tagged) => tagged.metadata.resourceType === resourceType); 48 15 return ( 49 - (this.policies.get(resourceType) as Policy<TResource> | undefined) ?? 16 + (found?.service as Policy<TResource> | undefined) ?? 50 17 raise(`No policy found for resource type: ${resourceType.name}`) 51 18 ); 52 19 } 53 20 54 21 hasPolicy(resourceType: Type<unknown>): boolean { 55 - return this.policies.has(resourceType); 56 - } 57 - 58 - private isPolicy(obj: unknown): obj is Policy<unknown> { 59 - if (!obj || typeof obj !== "object") { 60 - return false; 61 - } 62 - 63 - const prototype = Object.getPrototypeOf(obj); 64 - const policyMethods = ["view", "create", "update", "delete"]; 65 - 66 - return policyMethods.every( 67 - (method) => 68 - typeof (obj as Record<string, unknown>)[method] === "function" || 69 - typeof prototype[method] === "function", 70 - ); 22 + return this.locator 23 + .tagged(PolicyTag) 24 + .some((tagged) => tagged.metadata.resourceType === resourceType); 71 25 } 72 26 }
+13
packages/core/src/modules/auth/authorization/policy-tag.ts
··· 1 + import { Type } from "@nestjs/common"; 2 + import { defineTag } from "@riotbyte-com/nest-service-locator"; 3 + import { Policy } from "./policy.interface"; 4 + 5 + /** 6 + * Tag for resource policies. A class decorated with `@Policy(ResourceType)` 7 + * gets discovered at runtime by the ServiceLocator and routed by ResourceType 8 + * in `PolicyRegistry`. 9 + */ 10 + export const PolicyTag = defineTag< 11 + { resourceType: Type<unknown> }, 12 + Policy<unknown> 13 + >("policy");
+18 -3
packages/core/src/modules/auth/authorization/policy.decorator.ts
··· 1 - import type { Type } from "@nestjs/common"; 2 - import { SetMetadata } from "@nestjs/common"; 1 + import { Type } from "@nestjs/common"; 2 + import { Policy as IPolicy } from "./policy.interface"; 3 + import { PolicyTag } from "./policy-tag"; 3 4 5 + /** 6 + * Marks a class as a `Policy<TResource>` to be discovered by `PolicyRegistry`. 7 + * 8 + * Backed by `@cv/nest-service-locator`'s tagged-service mechanism: the 9 + * decorator stores the resourceType in tag metadata, and the registry pulls 10 + * tagged providers out of the DI graph at lookup time. 11 + * 12 + * Kept under the same `Policy(...)` name + `POLICY_RESOURCE_KEY` re-export 13 + * so existing call sites and any reflector-based consumers keep working. 14 + */ 4 15 export const POLICY_RESOURCE_KEY = "policy:resource"; 5 16 6 17 export const Policy = <TResource>(resourceType: Type<TResource>) => 7 - SetMetadata(POLICY_RESOURCE_KEY, resourceType); 18 + PolicyTag.decorator({ 19 + resourceType: resourceType as Type<unknown>, 20 + }) as <T extends IPolicy<TResource>>( 21 + target: new (...args: never[]) => T, 22 + ) => void;
+1 -1
packages/core/src/modules/auth/authorization/policy.interface.ts
··· 1 - import type { User } from "../user/user.entity"; 1 + import { User } from "../user/user.entity"; 2 2 3 3 export interface Policy<TResource = unknown> { 4 4 view(user: User, resource: TResource): boolean | Promise<boolean>;
+3 -6
packages/core/src/modules/auth/authorization/profile-owned-resource.policy.ts
··· 1 1 import { PrismaService } from "../../database"; 2 - import type { User } from "../user/user.entity"; 3 - import type { Policy } from "./policy.interface"; 2 + import { User } from "../user/user.entity"; 3 + import { Policy } from "./policy.interface"; 4 4 5 5 export abstract class ProfileOwnedResourcePolicy< 6 6 TResource extends { profileId: string }, ··· 12 12 return this.isOwner(user, resource); 13 13 } 14 14 15 - async create( 16 - user: User, 17 - resource?: Partial<TResource>, 18 - ): Promise<boolean> { 15 + async create(user: User, resource?: Partial<TResource>): Promise<boolean> { 19 16 if (!resource) return true; 20 17 if (!("profileId" in resource) || typeof resource.profileId !== "string") 21 18 return false;
+2 -2
packages/core/src/modules/auth/authorization/public-resource.policy.ts
··· 1 - import type { User } from "../user/user.entity"; 2 - import type { Policy } from "./policy.interface"; 1 + import { User } from "../user/user.entity"; 2 + import { Policy } from "./policy.interface"; 3 3 4 4 export abstract class PublicResourcePolicy<TResource = unknown> 5 5 implements Policy<TResource>
+2 -2
packages/core/src/modules/auth/authorization/user-owned-resource.policy.ts
··· 1 - import type { User } from "../user/user.entity"; 2 - import type { Policy } from "./policy.interface"; 1 + import { User } from "../user/user.entity"; 2 + import { Policy } from "./policy.interface"; 3 3 4 4 export abstract class UserOwnedResourcePolicy< 5 5 TResource extends { userId: string },
+3 -3
packages/core/src/modules/auth/identity-provider-registry.service.ts
··· 1 - import { raise } from "../../shared"; 2 1 import { Injectable, type OnModuleInit } from "@nestjs/common"; 3 2 import { DiscoveryService, Reflector } from "@nestjs/core"; 4 3 import { z } from "zod/v4"; 4 + import { raise } from "../../shared"; 5 5 import { 6 6 IDENTITY_PROVIDER_KEY, 7 - type IdentityProviderMeta, 7 + IdentityProviderMeta, 8 8 } from "./identity-provider.decorator"; 9 - import type { IdentityProvider } from "./identity-provider.interface"; 9 + import { IdentityProvider } from "./identity-provider.interface"; 10 10 11 11 type ProviderEntry<TUser> = { 12 12 meta: IdentityProviderMeta;
+2 -6
packages/core/src/modules/auth/providers/password/credentials/templated-email.service.ts
··· 1 1 import { 2 - HANDLEBARS_TEMPLATE_SERVICE_TOKEN, 3 - type HandlebarsTemplateService, 4 - MAIL_SERVICE_TOKEN, 5 - type MailService, 6 - } from "@cv/mail"; 2 + HANDLEBARS_TEMPLATE_SERVICE_TOKEN, HandlebarsTemplateService, MAIL_SERVICE_TOKEN, type MailService, } from "@cv/mail"; 7 3 import { Inject, Injectable } from "@nestjs/common"; 8 - import { EMAIL_CONFIG_TOKEN, type EmailConfig } from "./email.config"; 4 + import { EMAIL_CONFIG_TOKEN, EmailConfig } from "./email.config"; 9 5 10 6 export interface SendTemplatedEmailOptions { 11 7 to: string;
+2 -2
packages/core/src/modules/auth/providers/password/password-authentication.service.ts
··· 10 10 } from "../../errors/authentication.error"; 11 11 import { IdentityProviderRegistry } from "../../identity-provider-registry.service"; 12 12 import { JwtScope } from "../../jwt/jwt-scope.enum"; 13 - import type { RequestMetadata } from "../../request/request-metadata.decorator"; 13 + import { RequestMetadata } from "../../request/request-metadata.decorator"; 14 14 import { TokenService } from "../../token/token.service"; 15 15 import { TokenExpiryConfigService } from "../../token/token-expiry.config"; 16 16 import { CredentialsService } from "../../user/credentials.service"; 17 17 import { generateSecureToken } from "../../user/credentials-token.util"; 18 - import type { User } from "../../user/user.entity"; 18 + import { User } from "../../user/user.entity"; 19 19 import { UserService } from "../../user/user.service"; 20 20 import { PasswordResetRequestedEvent } from "./credentials/events/password-reset-requested.event"; 21 21 import { RegistrationAttemptOnExistingEmailEvent } from "./credentials/events/registration-attempt-on-existing-email.event";
+3 -3
packages/core/src/modules/auth/providers/password/password-identity-provider.ts
··· 1 - import { IdentityProviderDecorator } from "../.."; 2 1 import { Injectable } from "@nestjs/common"; 3 2 import * as bcrypt from "bcryptjs"; 3 + import { IdentityProviderDecorator } from "../.."; 4 4 import { InvalidCredentialsError } from "../../errors/authentication.error"; 5 - import type { IdentityProvider as IIdentityProvider } from "../../identity-provider.interface"; 5 + import { IdentityProvider as IIdentityProvider } from "../../identity-provider.interface"; 6 6 import { CredentialsService } from "../../user/credentials.service"; 7 - import type { User } from "../../user/user.entity"; 7 + import { User } from "../../user/user.entity"; 8 8 9 9 export const PASSWORD_PROVIDER_NAME = Symbol("password"); 10 10
+1 -1
packages/core/src/modules/auth/token/refresh-token.mapper.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 - import type { RefreshToken as PrismaRefreshToken } from "@prisma/client"; 2 + import { RefreshToken as PrismaRefreshToken } from "@prisma/client"; 3 3 import { RefreshToken } from "./refresh-token.entity"; 4 4 5 5 @Injectable()
+2 -2
packages/core/src/modules/auth/token/refresh-token.service.ts
··· 1 - import { PrismaService } from "../../database"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { PrismaService } from "../../database"; 3 3 import { InvalidRefreshTokenError, notFound } from "../errors"; 4 4 import { DeviceIdentificationService } from "../metadata/device-identification.service"; 5 5 import { LocationService } from "../metadata/location.service"; 6 6 import { hashToken } from "../user/credentials-token.util"; 7 7 import { TokenEncryptionService } from "../user/token-encryption.service"; 8 - import type { RefreshToken } from "./refresh-token.entity"; 8 + import { RefreshToken } from "./refresh-token.entity"; 9 9 import { RefreshTokenMapper } from "./refresh-token.mapper"; 10 10 11 11 @Injectable()
+1 -1
packages/core/src/modules/auth/token/token.module.ts
··· 1 - import { DatabaseModule } from "../../database"; 2 1 import { Module } from "@nestjs/common"; 3 2 import { ConfigModule } from "@nestjs/config"; 3 + import { DatabaseModule } from "../../database"; 4 4 import { JwtConfigService } from "../config/jwt.config"; 5 5 import { CookieService } from "../cookie/cookie.service"; 6 6 import { DeviceIdentificationService } from "../metadata/device-identification.service";
+6 -4
packages/core/src/modules/auth/token/token.service.ts
··· 3 3 import { JwtConfigService } from "../config/jwt.config"; 4 4 import { InvalidRefreshTokenError } from "../errors/authentication.error"; 5 5 import { JwtScope } from "../jwt/jwt-scope.enum"; 6 - import type { RequestMetadata } from "../request/request-metadata.decorator"; 7 - import type { User } from "../user/user.entity"; 6 + import { RequestMetadata } from "../request/request-metadata.decorator"; 7 + import { User } from "../user/user.entity"; 8 8 import { UserService } from "../user/user.service"; 9 9 import { RefreshTokenService } from "./refresh-token.service"; 10 10 import { TokenExpiryConfigService } from "./token-expiry.config"; ··· 36 36 const tokenScope = 37 37 scope ?? (isVerified ? JwtScope.VERIFIED : JwtScope.UNVERIFIED); 38 38 39 - const accessTokenExpirySeconds = this.jwtConfig.getAccessTokenExpirySeconds(); 39 + const accessTokenExpirySeconds = 40 + this.jwtConfig.getAccessTokenExpirySeconds(); 40 41 const refreshTokenExpiry = this.jwtConfig.getRefreshTokenExpiry(); 41 - const refreshTokenExpirySeconds = this.jwtConfig.getRefreshTokenExpirySeconds(); 42 + const refreshTokenExpirySeconds = 43 + this.jwtConfig.getRefreshTokenExpirySeconds(); 42 44 43 45 const refresh_token = this.jwtService.sign( 44 46 { sub: user.id, type: "refresh" },
+2 -2
packages/core/src/modules/auth/user/credentials.mapper.ts
··· 1 - import type { BaseMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { Credentials as PrismaCredentials } from "@prisma/client"; 2 + import { Credentials as PrismaCredentials } from "@prisma/client"; 3 + import { BaseMapper } from "../../../shared"; 4 4 import { Credentials } from "./credentials.entity"; 5 5 6 6 @Injectable()
+3 -3
packages/core/src/modules/auth/user/credentials.service.ts
··· 1 - import { PrismaService } from "../../database"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { PrismaService } from "../../database"; 3 3 import { notFound } from "../errors"; 4 4 import { 5 5 EmailAlreadyVerifiedError, ··· 8 8 PasswordResetTokenExpiredError, 9 9 VerificationTokenExpiredError, 10 10 } from "../errors/authentication.error"; 11 - import type { Credentials } from "./credentials.entity"; 11 + import { Credentials } from "./credentials.entity"; 12 12 import { CredentialsMapper } from "./credentials.mapper"; 13 13 import { hashToken, verifyToken } from "./credentials-token.util"; 14 14 import { TokenEncryptionService } from "./token-encryption.service"; 15 - import type { User } from "./user.entity"; 15 + import { User } from "./user.entity"; 16 16 import { UserMapper } from "./user.mapper"; 17 17 18 18 @Injectable()
+1 -1
packages/core/src/modules/auth/user/user.entity.ts
··· 1 1 import { BaseEntity } from "../../../shared"; 2 - import type { Credentials } from "./credentials.entity"; 2 + import { Credentials } from "./credentials.entity"; 3 3 4 4 export enum UserRole { 5 5 USER = "USER",
+2 -2
packages/core/src/modules/auth/user/user.mapper.ts
··· 1 - import type { BaseMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { Prisma } from "@prisma/client"; 2 + import { Prisma } from "@prisma/client"; 3 + import { BaseMapper } from "../../../shared"; 4 4 import { CredentialsMapper } from "./credentials.mapper"; 5 5 import { User, UserRole } from "./user.entity"; 6 6
+1 -1
packages/core/src/modules/auth/user/user.module.ts
··· 1 - import { DatabaseModule } from "../../database"; 2 1 import { Module } from "@nestjs/common"; 3 2 import { ConfigModule } from "@nestjs/config"; 3 + import { DatabaseModule } from "../../database"; 4 4 import { CredentialsMapper } from "./credentials.mapper"; 5 5 import { CredentialsService } from "./credentials.service"; 6 6 import { TokenEncryptionService } from "./token-encryption.service";
+2 -2
packages/core/src/modules/auth/user/user.service.ts
··· 1 - import { PrismaService } from "../../database"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { PrismaService } from "../../database"; 3 3 import { notFound } from "../errors"; 4 - import type { User } from "./user.entity"; 4 + import { User } from "./user.entity"; 5 5 import { UserMapper } from "./user.mapper"; 6 6 7 7 @Injectable()
+4 -4
packages/core/src/modules/authentication/authentication.module.ts
··· 1 + import { ResendModule, TemplateModule } from "@cv/mail"; 2 + import { Module } from "@nestjs/common"; 3 + import { ConfigModule } from "@nestjs/config"; 4 + import { BaseModule } from "../../shared"; 1 5 import { AuthorizationModule } from "../auth/authorization/authorization.module"; 2 6 import { TokenModule } from "../auth/token/token.module"; 3 7 import { UserModule } from "../auth/user/user.module"; 4 - import { BaseModule } from "../../shared"; 5 8 import { DatabaseModule } from "../database"; 6 - import { ResendModule, TemplateModule } from "@cv/mail"; 7 - import { Module } from "@nestjs/common"; 8 - import { ConfigModule } from "@nestjs/config"; 9 9 import { AuthenticationService } from "./authentication.service"; 10 10 import { PasswordProviderModule } from "./providers/password/password-provider.module"; 11 11
+2 -3
packages/core/src/modules/authentication/authentication.service.ts
··· 1 - import type { User } from "../auth"; 2 - import { UserService } from "../auth"; 3 1 import { Injectable } from "@nestjs/common"; 2 + import { User, UserService } from "../auth"; 4 3 export interface RefreshTokenDto { 5 4 refresh_token: string; 6 5 } ··· 14 13 refresh_token: string; 15 14 }; 16 15 17 - import type { RequestMetadata } from "../auth/request/request-metadata.decorator"; 18 16 import { TokenService } from "../auth"; 17 + import { RequestMetadata } from "../auth/request/request-metadata.decorator"; 19 18 20 19 @Injectable() 21 20 export class AuthenticationService {
+3 -3
packages/core/src/modules/authentication/providers/password/password-provider.module.ts
··· 1 + import { ResendModule, TemplateModule } from "@cv/mail"; 2 + import { Module } from "@nestjs/common"; 3 + import { ConfigModule, ConfigService } from "@nestjs/config"; 1 4 import { 2 5 PasswordProviderModule as AuthPasswordProviderModule, 3 6 EMAIL_CONFIG_TOKEN, ··· 7 10 TemplatedEmailService, 8 11 VerificationEmailListener, 9 12 } from "../../../auth"; 10 - import { ResendModule, TemplateModule } from "@cv/mail"; 11 - import { Module } from "@nestjs/common"; 12 - import { ConfigModule, ConfigService } from "@nestjs/config"; 13 13 14 14 @Module({ 15 15 imports: [
+1 -1
packages/core/src/modules/authentication/token/token.module.ts
··· 1 - import { TokenModule as AuthTokenModule } from "../../auth"; 2 1 import { Module } from "@nestjs/common"; 2 + import { TokenModule as AuthTokenModule } from "../../auth"; 3 3 4 4 @Module({ 5 5 imports: [AuthTokenModule],
+14 -4
packages/core/src/modules/cv-template/cv-data-assembler.service.ts
··· 1 - import { type CVRenderContext } from "@cv/cv-renderer"; 2 - import { PrismaService } from "../database"; 1 + import { CVRenderContext } from "@cv/cv-renderer"; 3 2 import { Injectable } from "@nestjs/common"; 3 + import { PrismaService } from "../database"; 4 4 import { ProfileService } from "../profile/profile.service"; 5 5 6 6 export type { CVRenderContext }; 7 7 8 8 const MONTHS = [ 9 - "Jan", "Feb", "Mar", "Apr", "May", "Jun", 10 - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 9 + "Jan", 10 + "Feb", 11 + "Mar", 12 + "Apr", 13 + "May", 14 + "Jun", 15 + "Jul", 16 + "Aug", 17 + "Sep", 18 + "Oct", 19 + "Nov", 20 + "Dec", 11 21 ] as const; 12 22 13 23 const formatDate = (date: Date): string =>
+1 -4
packages/core/src/modules/cv-template/cv-renderer.service.ts
··· 1 1 import { 2 - type TemplateEngine, 3 - HandlebarsEngine, 4 - wrapInDocument, 5 - } from "@cv/cv-renderer"; 2 + HandlebarsEngine, TemplateEngine, wrapInDocument, } from "@cv/cv-renderer"; 6 3 import { Injectable } from "@nestjs/common"; 7 4 import { CVDataAssemblerService } from "./cv-data-assembler.service"; 8 5 import { CVTemplateService } from "./cv-template.service";
+1 -1
packages/core/src/modules/cv-template/cv-template.mapper.ts
··· 1 - import type { CVTemplate as PrismaCVTemplate } from "@prisma/client"; 1 + import { CVTemplate as PrismaCVTemplate } from "@prisma/client"; 2 2 import { CVTemplate } from "./cv-template.entity"; 3 3 4 4 export const cvTemplateMapper = {
+5 -5
packages/core/src/modules/cv-template/cv-template.module.ts
··· 1 + import { forwardRef, Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../shared"; 1 3 import { AuthorizationModule } from "../auth"; 2 - import { BaseModule } from "../../shared"; 3 - import { DatabaseModule } from "../database"; 4 - import { Module, forwardRef } from "@nestjs/common"; 5 4 import { AuthenticationModule } from "../authentication/authentication.module"; 5 + import { DatabaseModule } from "../database"; 6 6 import { ProfileModule } from "../profile/profile.module"; 7 + import { CVPolicy } from "./cv.policy"; 8 + import { CVService } from "./cv.service"; 7 9 import { CVDataAssemblerService } from "./cv-data-assembler.service"; 8 10 import { CVRendererService } from "./cv-renderer.service"; 9 - import { CVPolicy } from "./cv.policy"; 10 - import { CVService } from "./cv.service"; 11 11 import { CVTemplatePolicy } from "./cv-template.policy"; 12 12 import { CVTemplateService } from "./cv-template.service"; 13 13 import { PdfDownloadController } from "./pdf-download.controller";
+1 -1
packages/core/src/modules/cv-template/cv-template.policy.ts
··· 1 - import { Policy, PublicResourcePolicy } from "../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, PublicResourcePolicy } from "../auth"; 3 3 import { CVTemplate } from "./cv-template.entity"; 4 4 5 5 @Injectable()
+1 -1
packages/core/src/modules/cv-template/cv-template.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { notFound } from "../auth"; 2 3 import { PrismaService } from "../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { cvTemplateMapper } from "./cv-template.mapper"; 5 5 6 6 type CVTemplateFilters = Record<string, never>;
+1 -1
packages/core/src/modules/cv-template/cv.entity.ts
··· 1 1 import { BaseEntity } from "../../shared"; 2 - import type { CVTemplate } from "./cv-template.entity"; 2 + import { CVTemplate } from "./cv-template.entity"; 3 3 4 4 export class CV extends BaseEntity { 5 5 profileId: string;
+2 -2
packages/core/src/modules/cv-template/cv.mapper.ts
··· 1 - import type { 1 + import { 2 2 CV as PrismaCV, 3 3 CVTemplate as PrismaCVTemplate, 4 4 } from "@prisma/client"; 5 - import { cvTemplateMapper } from "./cv-template.mapper"; 6 5 import { CV } from "./cv.entity"; 6 + import { cvTemplateMapper } from "./cv-template.mapper"; 7 7 8 8 export type PrismaCVWithTemplate = PrismaCV & { 9 9 template: PrismaCVTemplate;
+1 -1
packages/core/src/modules/cv-template/cv.policy.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { Policy, ProfileOwnedResourcePolicy } from "../auth"; 2 3 import { PrismaService } from "../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { CV } from "./cv.entity"; 5 5 6 6 @Injectable()
+4 -5
packages/core/src/modules/cv-template/cv.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 2 + import { Prisma } from "@prisma/client"; 3 + import { EntityService, PaginationOptions, PaginationResult, PaginationService } from "../../shared"; 1 4 import { notFound } from "../auth"; 2 - import type { PaginationOptions, PaginationResult } from "../../shared"; 3 - import { type EntityService, PaginationService } from "../../shared"; 4 5 import { PrismaService } from "../database"; 5 - import { Injectable } from "@nestjs/common"; 6 - import type { Prisma } from "@prisma/client"; 7 - import { cvMapper } from "./cv.mapper"; 8 6 import { CV } from "./cv.entity"; 7 + import { cvMapper } from "./cv.mapper"; 9 8 10 9 type CVFilters = { 11 10 profileId?: string | string[];
+2 -2
packages/core/src/modules/cv-template/index.ts
··· 1 1 export { CV } from "./cv.entity"; 2 - export { cvMapper } from "./cv.mapper"; 3 2 export type { PrismaCVWithTemplate } from "./cv.mapper"; 3 + export { cvMapper } from "./cv.mapper"; 4 4 export { CVPolicy } from "./cv.policy"; 5 5 export { CVService } from "./cv.service"; 6 - export { CVDataAssemblerService } from "./cv-data-assembler.service"; 7 6 export type { CVRenderContext } from "./cv-data-assembler.service"; 7 + export { CVDataAssemblerService } from "./cv-data-assembler.service"; 8 8 export { CVRendererService } from "./cv-renderer.service"; 9 9 export { CVTemplate, RenderedCV } from "./cv-template.entity"; 10 10 export { cvTemplateMapper } from "./cv-template.mapper";
+2 -3
packages/core/src/modules/cv-template/pdf-download.controller.ts
··· 1 - import type { User as DomainUser } from "../auth"; 2 - import { AuthorizationService } from "../auth"; 3 1 import { FILE_STORAGE, type FileStorage } from "@cv/file-storage"; 4 2 import { 5 3 Controller, ··· 11 9 Res, 12 10 } from "@nestjs/common"; 13 11 import type { Response } from "express"; 12 + import { AuthorizationService, User as DomainUser } from "../auth"; 13 + import { CV } from "./cv.entity"; 14 14 import { CVService } from "./cv.service"; 15 - import { CV } from "./cv.entity"; 16 15 17 16 @Controller("api/cv") 18 17 export class PdfDownloadController {
+1 -1
packages/core/src/modules/education/education.entity.ts
··· 1 1 import { BaseEntity } from "../../shared"; 2 - import type { Skill } from "../job-experience/skill/skill.entity"; 2 + import { Skill } from "../job-experience/skill/skill.entity"; 3 3 import { Institution } from "./institution.entity"; 4 4 5 5 export class Education extends BaseEntity {
+2 -2
packages/core/src/modules/education/education.mapper.ts
··· 1 - import type { BaseMapper } from "../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { Prisma } from "@prisma/client"; 2 + import { Prisma } from "@prisma/client"; 3 + import { BaseMapper } from "../../shared"; 4 4 import { SkillMapper } from "../job-experience/skill/skill.mapper"; 5 5 import { Education } from "./education.entity"; 6 6 import { InstitutionMapper } from "./institution.mapper";
+3 -3
packages/core/src/modules/education/education.module.ts
··· 1 - import { AuthorizationModule } from "../auth"; 2 - import { BaseModule } from "../../shared"; 3 - import { DatabaseModule } from "../database"; 4 1 import { Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../shared"; 3 + import { AuthorizationModule } from "../auth"; 5 4 import { AuthenticationModule } from "../authentication/authentication.module"; 5 + import { DatabaseModule } from "../database"; 6 6 import { SkillModule } from "../job-experience/skill/skill.module"; 7 7 import { EducationMapper } from "./education.mapper"; 8 8 import { EducationPolicy } from "./education.policy";
+1 -1
packages/core/src/modules/education/education.policy.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { Policy, ProfileOwnedResourcePolicy } from "../auth"; 2 3 import { PrismaService } from "../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { Education } from "./education.entity"; 5 5 6 6 @Injectable()
+3 -4
packages/core/src/modules/education/education.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 2 + import { Prisma } from "@prisma/client"; 3 + import { PaginationOptions, PaginationResult, PaginationService } from "../../shared"; 1 4 import { notFound } from "../auth"; 2 - import type { PaginationOptions, PaginationResult } from "../../shared"; 3 - import { PaginationService } from "../../shared"; 4 5 import { PrismaService } from "../database"; 5 - import { Injectable } from "@nestjs/common"; 6 - import type { Prisma } from "@prisma/client"; 7 6 import { Education } from "./education.entity"; 8 7 import { EducationMapper } from "./education.mapper"; 9 8
+1 -1
packages/core/src/modules/education/institution.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { ClockService, Factory, UuidFactoryService } from "../../shared"; 3 3 import { Institution } from "./institution.entity"; 4 4 5 5 type CreateInstitutionDto = {
+2 -2
packages/core/src/modules/education/institution.mapper.ts
··· 1 - import { createNamedEntityMapper } from "../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { Institution as PrismaInstitution } from "@prisma/client"; 2 + import { Institution as PrismaInstitution } from "@prisma/client"; 3 + import { createNamedEntityMapper } from "../../shared"; 4 4 import { Institution } from "./institution.entity"; 5 5 6 6 @Injectable()
+1 -1
packages/core/src/modules/education/institution.policy.ts
··· 1 - import { Policy, PublicResourcePolicy } from "../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, PublicResourcePolicy } from "../auth"; 3 3 import { Institution } from "./institution.entity"; 4 4 5 5 @Injectable()
+1 -1
packages/core/src/modules/education/institution.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { NamedEntityService } from "../../shared"; 2 3 import { PrismaService } from "../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { Institution } from "./institution.entity"; 5 5 import { InstitutionMapper } from "./institution.mapper"; 6 6
+1 -1
packages/core/src/modules/events/domain-event.ts
··· 16 16 */ 17 17 export abstract class DomainEvent<TPayload = void> { 18 18 static get eventName(): string { 19 - return this.name; 19 + return DomainEvent.name; 20 20 } 21 21 22 22 readonly eventName: string;
+1 -1
packages/core/src/modules/events/event.service.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 2 import { EventEmitter2 } from "@nestjs/event-emitter"; 3 - import type { DomainEvent } from "./domain-event"; 3 + import { DomainEvent } from "./domain-event"; 4 4 5 5 /** 6 6 * Typed wrapper around NestJS EventEmitter2.
+1 -1
packages/core/src/modules/job-experience/company/company.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "../../../shared"; 2 1 import { Injectable, Scope } from "@nestjs/common"; 2 + import { BaseDataLoaderService } from "../../../shared"; 3 3 import { Company } from "./company.entity"; 4 4 import { CompanyService } from "./company.service"; 5 5
+2 -2
packages/core/src/modules/job-experience/company/company.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { CreateCompanyDto } from "./company.dto"; 2 + import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 3 + import { CreateCompanyDto } from "./company.dto"; 4 4 import { Company } from "./company.entity"; 5 5 6 6 @Injectable()
+1 -1
packages/core/src/modules/job-experience/company/company.mapper.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 - import type { Company as PrismaCompany } from "@prisma/client"; 2 + import { Company as PrismaCompany } from "@prisma/client"; 3 3 import { Company } from "./company.entity"; 4 4 5 5 @Injectable()
+4 -4
packages/core/src/modules/job-experience/company/company.module.ts
··· 1 - import { AuthorizationModule } from "../../auth"; 2 - import { BaseModule } from "../../../shared"; 3 - import { DatabaseModule } from "../../database"; 4 1 import { Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../../shared"; 3 + import { AuthorizationModule } from "../../auth"; 5 4 import { AuthenticationModule } from "../../authentication/authentication.module"; 6 - import { CompanyService } from "./company.service"; 5 + import { DatabaseModule } from "../../database"; 7 6 import { CompanyDataLoaderService } from "./company.dataloader"; 8 7 import { CompanyFactory } from "./company.factory"; 9 8 import { CompanyMapper } from "./company.mapper"; 10 9 import { CompanyPolicy } from "./company.policy"; 10 + import { CompanyService } from "./company.service"; 11 11 12 12 @Module({ 13 13 imports: [
+1 -1
packages/core/src/modules/job-experience/company/company.policy.ts
··· 1 - import { Policy, PublicResourcePolicy } from "../../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, PublicResourcePolicy } from "../../auth"; 3 3 import { Company } from "./company.entity"; 4 4 5 5 @Injectable()
+1 -1
packages/core/src/modules/job-experience/company/company.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { NamedEntityService } from "../../../shared"; 2 3 import { PrismaService } from "../../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { Company } from "./company.entity"; 5 5 import { CompanyMapper } from "./company.mapper"; 6 6
+3 -3
packages/core/src/modules/job-experience/employment/employment.module.ts
··· 1 - import { AuthorizationModule } from "../../auth"; 2 - import { BaseModule } from "../../../shared"; 3 - import { DatabaseModule } from "../../database"; 4 1 import { Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../../shared"; 3 + import { AuthorizationModule } from "../../auth"; 5 4 import { AuthenticationModule } from "../../authentication/authentication.module"; 5 + import { DatabaseModule } from "../../database"; 6 6 import { CompanyModule } from "../company/company.module"; 7 7 import { LevelModule } from "../level/level.module"; 8 8 import { RoleModule } from "../role/role.module";
+4 -4
packages/core/src/modules/job-experience/employment/index.ts
··· 1 1 export { EmploymentModule } from "./employment.module"; 2 + export type { 3 + CreateUserJobExperienceDto, 4 + UpdateUserJobExperienceDto, 5 + } from "./user-job-experience.dto"; 2 6 export { UserJobExperience } from "./user-job-experience.entity"; 3 7 export { UserJobExperienceMapper } from "./user-job-experience.mapper"; 4 8 export { UserJobExperiencePolicy } from "./user-job-experience.policy"; 5 9 export { UserJobExperienceService } from "./user-job-experience.service"; 6 - export type { 7 - CreateUserJobExperienceDto, 8 - UpdateUserJobExperienceDto, 9 - } from "./user-job-experience.dto";
+4 -4
packages/core/src/modules/job-experience/employment/user-job-experience.dto.ts
··· 1 - import type { Company } from "../company/company.entity"; 2 - import type { Level } from "../level/level.entity"; 3 - import type { Role } from "../role/role.entity"; 4 - import type { Skill } from "../skill/skill.entity"; 1 + import { Company } from "../company/company.entity"; 2 + import { Level } from "../level/level.entity"; 3 + import { Role } from "../role/role.entity"; 4 + import { Skill } from "../skill/skill.entity"; 5 5 6 6 export interface CreateUserJobExperienceDto { 7 7 profileId: string;
+4 -4
packages/core/src/modules/job-experience/employment/user-job-experience.entity.ts
··· 1 1 import { BaseEntity } from "../../../shared"; 2 - import type { Company } from "../company/company.entity"; 3 - import type { Level } from "../level/level.entity"; 4 - import type { Role } from "../role/role.entity"; 5 - import type { Skill } from "../skill/skill.entity"; 2 + import { Company } from "../company/company.entity"; 3 + import { Level } from "../level/level.entity"; 4 + import { Role } from "../role/role.entity"; 5 + import { Skill } from "../skill/skill.entity"; 6 6 7 7 export class UserJobExperience extends BaseEntity { 8 8 profileId: string;
+2 -2
packages/core/src/modules/job-experience/employment/user-job-experience.mapper.ts
··· 1 - import type { BaseMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { 2 + import { 4 3 Company as PrismaCompany, 5 4 Level as PrismaLevel, 6 5 Profile as PrismaProfile, ··· 8 7 Skill as PrismaSkill, 9 8 UserJobExperience as PrismaUserJobExperience, 10 9 } from "@prisma/client"; 10 + import { BaseMapper } from "../../../shared"; 11 11 import { CompanyMapper } from "../company/company.mapper"; 12 12 import { LevelMapper } from "../level/level.mapper"; 13 13 import { RoleMapper } from "../role/role.mapper";
+1 -1
packages/core/src/modules/job-experience/employment/user-job-experience.policy.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { Policy, ProfileOwnedResourcePolicy } from "../../auth"; 2 3 import { PrismaService } from "../../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { UserJobExperience } from "./user-job-experience.entity"; 5 5 6 6 @Injectable()
+4 -5
packages/core/src/modules/job-experience/employment/user-job-experience.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 2 + import { Prisma } from "@prisma/client"; 3 + import { PaginationOptions, PaginationResult, PaginationService } from "../../../shared"; 1 4 import { notFound } from "../../auth"; 2 - import type { PaginationOptions, PaginationResult } from "../../../shared"; 3 - import { PaginationService } from "../../../shared"; 4 5 import { PrismaService } from "../../database"; 5 - import { Injectable } from "@nestjs/common"; 6 - import type { Prisma } from "@prisma/client"; 7 - import type { 6 + import { 8 7 CreateUserJobExperienceDto, 9 8 UpdateUserJobExperienceDto, 10 9 } from "./user-job-experience.dto";
+1 -1
packages/core/src/modules/job-experience/level/level.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "../../../shared"; 2 1 import { Injectable, Scope } from "@nestjs/common"; 2 + import { BaseDataLoaderService } from "../../../shared"; 3 3 import { Level } from "./level.entity"; 4 4 import { LevelService } from "./level.service"; 5 5
+2 -2
packages/core/src/modules/job-experience/level/level.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { CreateLevelDto } from "./level.dto"; 2 + import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 3 + import { CreateLevelDto } from "./level.dto"; 4 4 import { Level } from "./level.entity"; 5 5 6 6 @Injectable()
+2 -2
packages/core/src/modules/job-experience/level/level.mapper.ts
··· 1 - import { createNamedEntityMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { Level as PrismaLevel } from "@prisma/client"; 2 + import { Level as PrismaLevel } from "@prisma/client"; 3 + import { createNamedEntityMapper } from "../../../shared"; 4 4 import { Level } from "./level.entity"; 5 5 6 6 /**
+4 -4
packages/core/src/modules/job-experience/level/level.module.ts
··· 1 - import { AuthorizationModule } from "../../auth"; 2 - import { BaseModule } from "../../../shared"; 3 - import { DatabaseModule } from "../../database"; 4 1 import { Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../../shared"; 3 + import { AuthorizationModule } from "../../auth"; 5 4 import { AuthenticationModule } from "../../authentication/authentication.module"; 6 - import { LevelService } from "./level.service"; 5 + import { DatabaseModule } from "../../database"; 7 6 import { LevelDataLoaderService } from "./level.dataloader"; 8 7 import { LevelFactory } from "./level.factory"; 9 8 import { LevelMapper } from "./level.mapper"; 10 9 import { LevelPolicy } from "./level.policy"; 10 + import { LevelService } from "./level.service"; 11 11 12 12 @Module({ 13 13 imports: [
+1 -1
packages/core/src/modules/job-experience/level/level.policy.ts
··· 1 - import { Policy, PublicResourcePolicy } from "../../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, PublicResourcePolicy } from "../../auth"; 3 3 import { Level } from "./level.entity"; 4 4 5 5 @Injectable()
+1 -1
packages/core/src/modules/job-experience/level/level.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { NamedEntityService } from "../../../shared"; 2 3 import { PrismaService } from "../../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { Level } from "./level.entity"; 5 5 import { LevelMapper } from "./level.mapper"; 6 6
+1 -1
packages/core/src/modules/job-experience/role/role.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "../../../shared"; 2 1 import { Injectable, Scope } from "@nestjs/common"; 2 + import { BaseDataLoaderService } from "../../../shared"; 3 3 import { Role } from "./role.entity"; 4 4 import { RoleService } from "./role.service"; 5 5
+2 -2
packages/core/src/modules/job-experience/role/role.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { CreateRoleDto } from "./role.dto"; 2 + import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 3 + import { CreateRoleDto } from "./role.dto"; 4 4 import { Role } from "./role.entity"; 5 5 6 6 @Injectable()
+2 -2
packages/core/src/modules/job-experience/role/role.mapper.ts
··· 1 - import { createNamedEntityMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { Role as PrismaRole } from "@prisma/client"; 2 + import { Role as PrismaRole } from "@prisma/client"; 3 + import { createNamedEntityMapper } from "../../../shared"; 4 4 import { Role } from "./role.entity"; 5 5 6 6 /**
+4 -4
packages/core/src/modules/job-experience/role/role.module.ts
··· 1 - import { AuthorizationModule } from "../../auth"; 2 - import { BaseModule } from "../../../shared"; 3 - import { DatabaseModule } from "../../database"; 4 1 import { Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../../shared"; 3 + import { AuthorizationModule } from "../../auth"; 5 4 import { AuthenticationModule } from "../../authentication/authentication.module"; 6 - import { RoleService } from "./role.service"; 5 + import { DatabaseModule } from "../../database"; 7 6 import { RoleDataLoaderService } from "./role.dataloader"; 8 7 import { RoleFactory } from "./role.factory"; 9 8 import { RoleMapper } from "./role.mapper"; 10 9 import { RolePolicy } from "./role.policy"; 10 + import { RoleService } from "./role.service"; 11 11 12 12 @Module({ 13 13 imports: [
+1 -1
packages/core/src/modules/job-experience/role/role.policy.ts
··· 1 - import { Policy, PublicResourcePolicy } from "../../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, PublicResourcePolicy } from "../../auth"; 3 3 import { Role } from "./role.entity"; 4 4 5 5 @Injectable()
+1 -1
packages/core/src/modules/job-experience/role/role.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { NamedEntityService } from "../../../shared"; 2 3 import { PrismaService } from "../../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { Role } from "./role.entity"; 5 5 import { RoleMapper } from "./role.mapper"; 6 6
+2 -2
packages/core/src/modules/job-experience/skill/skill.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { CreateSkillDto } from "./skill.dto"; 2 + import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 3 + import { CreateSkillDto } from "./skill.dto"; 4 4 import { Skill } from "./skill.entity"; 5 5 6 6 @Injectable()
+2 -2
packages/core/src/modules/job-experience/skill/skill.mapper.ts
··· 1 - import { createNamedEntityMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { Skill as PrismaSkill } from "@prisma/client"; 2 + import { Skill as PrismaSkill } from "@prisma/client"; 3 + import { createNamedEntityMapper } from "../../../shared"; 4 4 import { Skill } from "./skill.entity"; 5 5 6 6 /**
+4 -4
packages/core/src/modules/job-experience/skill/skill.module.ts
··· 1 - import { AuthorizationModule } from "../../auth"; 2 - import { BaseModule } from "../../../shared"; 3 - import { DatabaseModule } from "../../database"; 4 1 import { Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../../shared"; 3 + import { AuthorizationModule } from "../../auth"; 5 4 import { AuthenticationModule } from "../../authentication/authentication.module"; 6 - import { SkillService } from "./skill.service"; 5 + import { DatabaseModule } from "../../database"; 7 6 import { SkillFactory } from "./skill.factory"; 8 7 import { SkillMapper } from "./skill.mapper"; 9 8 import { SkillPolicy } from "./skill.policy"; 9 + import { SkillService } from "./skill.service"; 10 10 11 11 @Module({ 12 12 imports: [
+1 -1
packages/core/src/modules/job-experience/skill/skill.policy.ts
··· 1 - import { Policy, PublicResourcePolicy } from "../../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, PublicResourcePolicy } from "../../auth"; 3 3 import { Skill } from "./skill.entity"; 4 4 5 5 @Injectable()
+2 -2
packages/core/src/modules/job-experience/skill/skill.service.ts
··· 1 - import { type NamedEntityFilters, NamedEntityService } from "../../../shared"; 2 - import { PrismaService } from "../../database"; 3 1 import { Injectable } from "@nestjs/common"; 2 + import { NamedEntityFilters, NamedEntityService } from "../../../shared"; 3 + import { PrismaService } from "../../database"; 4 4 import { Skill } from "./skill.entity"; 5 5 import { SkillMapper } from "./skill.mapper"; 6 6
+1 -1
packages/core/src/modules/organization/index.ts
··· 1 - export { Organization } from "./organization.entity"; 2 1 export type { 3 2 CreateOrganizationDto, 4 3 UpdateOrganizationDto, 5 4 } from "./organization.dto"; 5 + export { Organization } from "./organization.entity"; 6 6 export { OrganizationFactory } from "./organization.factory"; 7 7 export { OrganizationMapper } from "./organization.mapper"; 8 8 export { OrganizationModule } from "./organization.module";
+1 -1
packages/core/src/modules/organization/organization-role.entity.ts
··· 1 - import { BaseEntity } from "../../shared"; 2 1 import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import { BaseEntity } from "../../shared"; 3 3 4 4 @ObjectType() 5 5 export class OrganizationRole extends BaseEntity {
+2 -2
packages/core/src/modules/organization/organization-role.mapper.ts
··· 1 - import type { BaseMapper } from "../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { OrganizationRole as PrismaOrganizationRole } from "@prisma/client"; 2 + import { OrganizationRole as PrismaOrganizationRole } from "@prisma/client"; 3 + import { BaseMapper } from "../../shared"; 4 4 import { OrganizationRole } from "./organization-role.entity"; 5 5 6 6 @Injectable()
+1 -1
packages/core/src/modules/organization/organization-role.policy.ts
··· 1 - import { Policy, PublicResourcePolicy } from "../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, PublicResourcePolicy } from "../auth"; 3 3 import { OrganizationRole } from "./organization-role.entity"; 4 4 5 5 @Injectable()
+1 -1
packages/core/src/modules/organization/organization-role.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { notFound } from "../auth"; 2 3 import { PrismaService } from "../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { OrganizationRole } from "./organization-role.entity"; 5 5 import { OrganizationRoleMapper } from "./organization-role.mapper"; 6 6
+1 -1
packages/core/src/modules/organization/organization.entity.ts
··· 1 - import { BaseEntity } from "../../shared"; 2 1 import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import { BaseEntity } from "../../shared"; 3 3 4 4 @ObjectType() 5 5 export class Organization extends BaseEntity {
+2 -2
packages/core/src/modules/organization/organization.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { CreateOrganizationDto } from "./organization.dto"; 2 + import { ClockService, Factory, UuidFactoryService } from "../../shared"; 3 + import { CreateOrganizationDto } from "./organization.dto"; 4 4 import { Organization } from "./organization.entity"; 5 5 6 6 @Injectable()
+2 -2
packages/core/src/modules/organization/organization.mapper.ts
··· 1 - import type { BaseMapper } from "../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { Organization as PrismaOrganization } from "@prisma/client"; 2 + import { Organization as PrismaOrganization } from "@prisma/client"; 3 + import { BaseMapper } from "../../shared"; 4 4 import { Organization } from "./organization.entity"; 5 5 6 6 @Injectable()
+3 -3
packages/core/src/modules/organization/organization.module.ts
··· 1 - import { AuthorizationModule } from "../auth"; 2 - import { BaseModule } from "../../shared"; 3 - import { DatabaseModule } from "../database"; 4 1 import { Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../shared"; 3 + import { AuthorizationModule } from "../auth"; 5 4 import { AuthenticationModule } from "../authentication/authentication.module"; 5 + import { DatabaseModule } from "../database"; 6 6 import { OrganizationFactory } from "./organization.factory"; 7 7 import { OrganizationMapper } from "./organization.mapper"; 8 8 import { OrganizationPolicy } from "./organization.policy";
+1 -1
packages/core/src/modules/organization/organization.policy.ts
··· 1 - import { Policy, PublicResourcePolicy } from "../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, PublicResourcePolicy } from "../auth"; 3 3 import { Organization } from "./organization.entity"; 4 4 5 5 @Injectable()
+3 -3
packages/core/src/modules/organization/organization.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 2 + import { Prisma } from "@prisma/client"; 3 + import { EntityService } from "../../shared"; 1 4 import { notFound, User } from "../auth"; 2 - import { type EntityService } from "../../shared"; 3 5 import { PrismaService } from "../database"; 4 - import { Injectable } from "@nestjs/common"; 5 - import type { Prisma } from "@prisma/client"; 6 6 import { Organization } from "./organization.entity"; 7 7 import { OrganizationMapper } from "./organization.mapper"; 8 8
+3 -3
packages/core/src/modules/profile/profile.module.ts
··· 1 - import { AuthorizationModule } from "../auth/authorization/authorization.module"; 1 + import { forwardRef, Module } from "@nestjs/common"; 2 2 import { BaseModule } from "../../shared"; 3 - import { DatabaseModule } from "../database"; 4 - import { Module, forwardRef } from "@nestjs/common"; 3 + import { AuthorizationModule } from "../auth/authorization/authorization.module"; 5 4 import { CVTemplateModule } from "../cv-template/cv-template.module"; 5 + import { DatabaseModule } from "../database"; 6 6 import { EducationModule } from "../education/education.module"; 7 7 import { EmploymentModule } from "../job-experience/employment/employment.module"; 8 8 import { ProfilePolicy } from "./profile.policy";
+1 -1
packages/core/src/modules/profile/profile.policy.ts
··· 1 - import { Policy, UserOwnedResourcePolicy } from "../auth"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { Policy, UserOwnedResourcePolicy } from "../auth"; 3 3 import { ProfileEntity } from "./profile.entity"; 4 4 5 5 @Injectable()
+7 -4
packages/core/src/modules/profile/profile.service.ts
··· 1 - import type { User } from "../auth"; 2 - import { TokenEncryptionService } from "../auth"; 1 + import { Injectable, NotFoundException } from "@nestjs/common"; 2 + import { TokenEncryptionService, User } from "../auth"; 3 3 import { PrismaService } from "../database"; 4 - import { Injectable, NotFoundException } from "@nestjs/common"; 5 4 6 5 interface CreateProfileInput { 7 6 name: string; ··· 144 143 select: { headline: true, locationId: true }, 145 144 }); 146 145 147 - return profile !== null && profile.headline !== null && profile.locationId !== null; 146 + return ( 147 + profile !== null && 148 + profile.headline !== null && 149 + profile.locationId !== null 150 + ); 148 151 } 149 152 150 153 private decrypt(profile: {
+5 -2
packages/core/src/modules/vacancies/contract-type/contract-type.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "../../../shared"; 2 1 import { Injectable, Scope } from "@nestjs/common"; 2 + import { BaseDataLoaderService } from "../../../shared"; 3 3 import { ContractType } from "./contract-type.entity"; 4 4 import { ContractTypeService } from "./contract-type.service"; 5 5 6 6 @Injectable({ scope: Scope.REQUEST }) 7 - export class ContractTypeDataLoaderService extends BaseDataLoaderService<string, ContractType> { 7 + export class ContractTypeDataLoaderService extends BaseDataLoaderService< 8 + string, 9 + ContractType 10 + > { 8 11 constructor(service: ContractTypeService) { 9 12 super(async (ids: readonly string[]) => { 10 13 const items = await service.findMany({ id: [...ids] });
+5 -3
packages/core/src/modules/vacancies/contract-type/contract-type.mapper.ts
··· 1 - import { createNamedEntityMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { ContractType as PrismaContractType } from "@prisma/client"; 2 + import { ContractType as PrismaContractType } from "@prisma/client"; 3 + import { createNamedEntityMapper } from "../../../shared"; 4 4 import { ContractType } from "./contract-type.entity"; 5 5 6 6 @Injectable() 7 7 export class ContractTypeMapper { 8 - private mapper = createNamedEntityMapper<PrismaContractType, ContractType>(ContractType); 8 + private mapper = createNamedEntityMapper<PrismaContractType, ContractType>( 9 + ContractType, 10 + ); 9 11 10 12 toDomain(prisma: null): null; 11 13 toDomain(prisma: PrismaContractType): ContractType;
+6 -2
packages/core/src/modules/vacancies/contract-type/contract-type.module.ts
··· 1 - import { DatabaseModule } from "../../database"; 2 1 import { Module } from "@nestjs/common"; 2 + import { DatabaseModule } from "../../database"; 3 3 import { ContractTypeDataLoaderService } from "./contract-type.dataloader"; 4 4 import { ContractTypeMapper } from "./contract-type.mapper"; 5 5 import { ContractTypeService } from "./contract-type.service"; 6 6 7 7 @Module({ 8 8 imports: [DatabaseModule], 9 - providers: [ContractTypeService, ContractTypeMapper, ContractTypeDataLoaderService], 9 + providers: [ 10 + ContractTypeService, 11 + ContractTypeMapper, 12 + ContractTypeDataLoaderService, 13 + ], 10 14 exports: [ContractTypeService, ContractTypeDataLoaderService], 11 15 }) 12 16 export class ContractTypeModule {}
+1 -1
packages/core/src/modules/vacancies/contract-type/contract-type.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { NamedEntityService } from "../../../shared"; 2 3 import { PrismaService } from "../../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { ContractType } from "./contract-type.entity"; 5 5 import { ContractTypeMapper } from "./contract-type.mapper"; 6 6
+14 -18
packages/core/src/modules/vacancies/index.ts
··· 1 - export type { VacancyFilter } from "./vacancy-filter.types"; 2 - export { Vacancy } from "./vacancy.entity"; 3 - export { 4 - type CreateVacancyDto, 5 - VacancyFactory, 6 - } from "./vacancy.factory"; 7 - export { VacancyMapper } from "./vacancy.mapper"; 8 - export { VacancyModule } from "./vacancy.module"; 9 - export { VacancyPolicy } from "./vacancy.policy"; 10 - export { VacancyService } from "./vacancy.service"; 11 - 12 - export { ContractType } from "./contract-type/contract-type.entity"; 13 1 export { ContractTypeDataLoaderService } from "./contract-type/contract-type.dataloader"; 2 + export { ContractType } from "./contract-type/contract-type.entity"; 14 3 export { ContractTypeMapper } from "./contract-type/contract-type.mapper"; 15 4 export { ContractTypeModule } from "./contract-type/contract-type.module"; 16 5 export { ContractTypeService } from "./contract-type/contract-type.service"; 17 - 18 - export { JobType } from "./job-type/job-type.entity"; 19 6 export type { 20 7 CreateJobTypeDto, 21 8 UpdateJobTypeDto, 22 9 } from "./job-type/job-type.dto"; 10 + export { JobType } from "./job-type/job-type.entity"; 23 11 export { JobTypeFactory } from "./job-type/job-type.factory"; 24 12 export { JobTypeMapper } from "./job-type/job-type.mapper"; 25 13 export { JobTypeModule } from "./job-type/job-type.module"; 26 14 export { JobTypeService } from "./job-type/job-type.service"; 27 - 15 + export { LocationDataLoaderService } from "./location/location.dataloader"; 28 16 export { Location, LocationType } from "./location/location.entity"; 29 - export { LocationDataLoaderService } from "./location/location.dataloader"; 30 17 export { LocationMapper } from "./location/location.mapper"; 31 18 export { LocationModule } from "./location/location.module"; 32 19 // Renamed to disambiguate from auth's LocationService (geolocation lookup). 33 20 export { LocationService as VacancyLocationService } from "./location/location.service"; 34 - 35 - export { RateType } from "./rate-type/rate-type.entity"; 36 21 export { RateTypeDataLoaderService } from "./rate-type/rate-type.dataloader"; 22 + export { RateType } from "./rate-type/rate-type.entity"; 37 23 export { RateTypeMapper } from "./rate-type/rate-type.mapper"; 38 24 export { RateTypeModule } from "./rate-type/rate-type.module"; 39 25 export { RateTypeService } from "./rate-type/rate-type.service"; 26 + export { Vacancy } from "./vacancy.entity"; 27 + export { 28 + type CreateVacancyDto, 29 + VacancyFactory, 30 + } from "./vacancy.factory"; 31 + export { VacancyMapper } from "./vacancy.mapper"; 32 + export { VacancyModule } from "./vacancy.module"; 33 + export { VacancyPolicy } from "./vacancy.policy"; 34 + export { VacancyService } from "./vacancy.service"; 35 + export type { VacancyFilter } from "./vacancy-filter.types";
+6 -2
packages/core/src/modules/vacancies/job-type/job-type.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { CreateJobTypeDto } from "./job-type.dto"; 2 + import { 3 + ClockService, 4 + Factory, 5 + UuidFactoryService, 6 + } from "../../../shared"; 7 + import { CreateJobTypeDto } from "./job-type.dto"; 4 8 import { JobType } from "./job-type.entity"; 5 9 6 10 @Injectable()
+2 -2
packages/core/src/modules/vacancies/job-type/job-type.mapper.ts
··· 1 - import { createNamedEntityMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { JobType as PrismaJobType } from "@prisma/client"; 2 + import { JobType as PrismaJobType } from "@prisma/client"; 3 + import { createNamedEntityMapper } from "../../../shared"; 4 4 import { JobType } from "./job-type.entity"; 5 5 6 6 @Injectable()
+1 -1
packages/core/src/modules/vacancies/job-type/job-type.module.ts
··· 1 + import { Module } from "@nestjs/common"; 1 2 import { BaseModule } from "../../../shared"; 2 3 import { DatabaseModule } from "../../database"; 3 - import { Module } from "@nestjs/common"; 4 4 import { JobTypeFactory } from "./job-type.factory"; 5 5 import { JobTypeMapper } from "./job-type.mapper"; 6 6 import { JobTypeService } from "./job-type.service";
+1 -1
packages/core/src/modules/vacancies/job-type/job-type.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { NamedEntityService } from "../../../shared"; 2 3 import { PrismaService } from "../../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { JobType } from "./job-type.entity"; 5 5 import { JobTypeMapper } from "./job-type.mapper"; 6 6
+5 -2
packages/core/src/modules/vacancies/location/location.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "../../../shared"; 2 1 import { Injectable, Scope } from "@nestjs/common"; 2 + import { BaseDataLoaderService } from "../../../shared"; 3 3 import { Location } from "./location.entity"; 4 4 import { LocationService } from "./location.service"; 5 5 6 6 @Injectable({ scope: Scope.REQUEST }) 7 - export class LocationDataLoaderService extends BaseDataLoaderService<string, Location> { 7 + export class LocationDataLoaderService extends BaseDataLoaderService< 8 + string, 9 + Location 10 + > { 8 11 constructor(service: LocationService) { 9 12 super(async (ids: readonly string[]) => { 10 13 const items = await service.findMany({ id: [...ids] });
+4 -2
packages/core/src/modules/vacancies/location/location.mapper.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 - import type { Location as PrismaLocation } from "@prisma/client"; 2 + import { Location as PrismaLocation } from "@prisma/client"; 3 3 import { Location } from "./location.entity"; 4 4 5 5 @Injectable() ··· 21 21 } 22 22 23 23 mapToDomain(items: PrismaLocation[]): Location[] { 24 - return items.map((item) => this.toDomain(item)).filter((item): item is Location => item !== null); 24 + return items 25 + .map((item) => this.toDomain(item)) 26 + .filter((item): item is Location => item !== null); 25 27 } 26 28 }
+1 -1
packages/core/src/modules/vacancies/location/location.module.ts
··· 1 - import { DatabaseModule } from "../../database"; 2 1 import { Module } from "@nestjs/common"; 2 + import { DatabaseModule } from "../../database"; 3 3 import { LocationDataLoaderService } from "./location.dataloader"; 4 4 import { LocationMapper } from "./location.mapper"; 5 5 import { LocationService } from "./location.service";
+1 -1
packages/core/src/modules/vacancies/location/location.service.ts
··· 1 - import { PrismaService } from "../../database"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { PrismaService } from "../../database"; 3 3 import { Location } from "./location.entity"; 4 4 import { LocationMapper } from "./location.mapper"; 5 5
+5 -2
packages/core/src/modules/vacancies/rate-type/rate-type.dataloader.ts
··· 1 - import { BaseDataLoaderService } from "../../../shared"; 2 1 import { Injectable, Scope } from "@nestjs/common"; 2 + import { BaseDataLoaderService } from "../../../shared"; 3 3 import { RateType } from "./rate-type.entity"; 4 4 import { RateTypeService } from "./rate-type.service"; 5 5 6 6 @Injectable({ scope: Scope.REQUEST }) 7 - export class RateTypeDataLoaderService extends BaseDataLoaderService<string, RateType> { 7 + export class RateTypeDataLoaderService extends BaseDataLoaderService< 8 + string, 9 + RateType 10 + > { 8 11 constructor(service: RateTypeService) { 9 12 super(async (ids: readonly string[]) => { 10 13 const items = await service.findMany({ id: [...ids] });
+2 -2
packages/core/src/modules/vacancies/rate-type/rate-type.mapper.ts
··· 1 - import { createNamedEntityMapper } from "../../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 3 - import type { RateType as PrismaRateType } from "@prisma/client"; 2 + import { RateType as PrismaRateType } from "@prisma/client"; 3 + import { createNamedEntityMapper } from "../../../shared"; 4 4 import { RateType } from "./rate-type.entity"; 5 5 6 6 @Injectable()
+1 -1
packages/core/src/modules/vacancies/rate-type/rate-type.module.ts
··· 1 - import { DatabaseModule } from "../../database"; 2 1 import { Module } from "@nestjs/common"; 2 + import { DatabaseModule } from "../../database"; 3 3 import { RateTypeDataLoaderService } from "./rate-type.dataloader"; 4 4 import { RateTypeMapper } from "./rate-type.mapper"; 5 5 import { RateTypeService } from "./rate-type.service";
+1 -1
packages/core/src/modules/vacancies/rate-type/rate-type.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 1 2 import { NamedEntityService } from "../../../shared"; 2 3 import { PrismaService } from "../../database"; 3 - import { Injectable } from "@nestjs/common"; 4 4 import { RateType } from "./rate-type.entity"; 5 5 import { RateTypeMapper } from "./rate-type.mapper"; 6 6
+1 -1
packages/core/src/modules/vacancies/vacancy.factory.ts
··· 1 - import { ClockService, Factory, UuidFactoryService } from "../../shared"; 2 1 import { Injectable } from "@nestjs/common"; 2 + import { ClockService, Factory, UuidFactoryService } from "../../shared"; 3 3 import { Company } from "../job-experience/company/company.entity"; 4 4 import { Level } from "../job-experience/level/level.entity"; 5 5 import { Role } from "../job-experience/role/role.entity";
+1 -1
packages/core/src/modules/vacancies/vacancy.mapper.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 - import type { Prisma } from "@prisma/client"; 2 + import { Prisma } from "@prisma/client"; 3 3 import { CompanyMapper } from "../job-experience/company/company.mapper"; 4 4 import { LevelMapper } from "../job-experience/level/level.mapper"; 5 5 import { RoleMapper } from "../job-experience/role/role.mapper";
+3 -3
packages/core/src/modules/vacancies/vacancy.module.ts
··· 1 - import { AuthorizationModule } from "../auth"; 2 - import { BaseModule } from "../../shared"; 3 - import { DatabaseModule } from "../database"; 4 1 import { Module } from "@nestjs/common"; 2 + import { BaseModule } from "../../shared"; 3 + import { AuthorizationModule } from "../auth"; 5 4 import { AuthenticationModule } from "../authentication/authentication.module"; 5 + import { DatabaseModule } from "../database"; 6 6 import { CompanyModule } from "../job-experience/company/company.module"; 7 7 import { LevelModule } from "../job-experience/level/level.module"; 8 8 import { RoleModule } from "../job-experience/role/role.module";
+2 -2
packages/core/src/modules/vacancies/vacancy.policy.ts
··· 1 - import type { User } from "../auth"; 2 - import { OwnerOwnedResourcePolicy, Policy } from "../auth"; 3 1 import { Injectable } from "@nestjs/common"; 2 + import { User } from "../auth"; 3 + import { OwnerOwnedResourcePolicy, Policy } from "../auth"; 4 4 import { Vacancy } from "./vacancy.entity"; 5 5 6 6 @Injectable()
+5 -5
packages/core/src/modules/vacancies/vacancy.service.ts
··· 1 - import { notFound } from "../auth"; 2 - import { type EntityService, raise } from "../../shared"; 3 - import { PrismaService } from "../database"; 4 1 import { ensureArray } from "@cv/utils"; 5 2 import { Injectable } from "@nestjs/common"; 6 - import type { Prisma } from "@prisma/client"; 7 - import { VacancyFilter } from "./vacancy-filter.types"; 3 + import { Prisma } from "@prisma/client"; 4 + import { EntityService, raise } from "../../shared"; 5 + import { notFound } from "../auth"; 6 + import { PrismaService } from "../database"; 8 7 import { Vacancy } from "./vacancy.entity"; 9 8 import { VacancyMapper } from "./vacancy.mapper"; 9 + import { VacancyFilter } from "./vacancy-filter.types"; 10 10 11 11 type VacancyFilters = { 12 12 filter?: VacancyFilter;
+1 -1
packages/core/src/shared/dataloader.service.ts
··· 1 1 import { Injectable, Scope } from "@nestjs/common"; 2 2 import DataLoader from "dataloader"; 3 - import type { BaseEntity } from "./base.entity"; 3 + import { BaseEntity } from "./base.entity"; 4 4 5 5 export type { DataLoader }; 6 6
+1 -1
packages/core/src/shared/entity-service.interface.ts
··· 1 - import type { BaseEntity } from "./base.entity"; 1 + import { BaseEntity } from "./base.entity"; 2 2 3 3 export interface EntityService< 4 4 T extends BaseEntity,
+1 -1
packages/core/src/shared/factory.interface.ts
··· 1 - import type { BaseEntity } from "./base.entity"; 1 + import { BaseEntity } from "./base.entity"; 2 2 3 3 export interface Factory<TEntity extends BaseEntity, TData> { 4 4 create(data: TData): TEntity;
+2 -2
packages/core/src/shared/index.ts
··· 1 1 export * from "./base.entity"; 2 - export * from "./create-gql-param-decorator"; 3 2 export * from "./base.module"; 4 3 export * from "./clock.service"; 5 4 export * from "./connection.types"; 5 + export * from "./create-gql-param-decorator"; 6 6 export * from "./cursor.service"; 7 7 export * from "./dataloader.service"; 8 8 export * from "./entity-service.interface"; 9 + export * from "./errors"; 9 10 export * from "./factory.interface"; 10 11 export * from "./mapper.interface"; 11 12 export * from "./named-entity"; ··· 16 17 export * from "./raise.util"; 17 18 export * from "./unauthorized.util"; 18 19 export * from "./uuid-factory.service"; 19 - export * from "./errors";
+2 -2
packages/core/src/shared/named-entity.mapper.ts
··· 1 - import type { BaseMapper } from "./mapper.interface"; 2 - import type { NamedEntity } from "./named-entity"; 1 + import { BaseMapper } from "./mapper.interface"; 2 + import { NamedEntity } from "./named-entity"; 3 3 4 4 /** 5 5 * Creates a mapper for NamedEntity types
+3 -3
packages/core/src/shared/named-entity.service.ts
··· 1 1 import { PrismaService } from "../modules/database/prisma.service"; 2 - import type { EntityService } from "./entity-service.interface"; 3 - import type { BaseMapper } from "./mapper.interface"; 4 - import type { NamedEntity } from "./named-entity"; 2 + import { EntityService } from "./entity-service.interface"; 3 + import { BaseMapper } from "./mapper.interface"; 4 + import { NamedEntity } from "./named-entity"; 5 5 6 6 /** 7 7 * Base filters for NamedEntity services
+3 -3
packages/core/src/shared/pagination.service.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 - import type { BaseEntity } from "./base.entity"; 2 + import { BaseEntity } from "./base.entity"; 3 3 import { CursorService } from "./cursor.service"; 4 4 import { 5 5 PageInfo, 6 - type PaginationOptions, 7 - type PaginationResult, 6 + PaginationOptions, 7 + PaginationResult, 8 8 } from "./pagination.types"; 9 9 10 10 @Injectable()
+17 -14
packages/cv-renderer/src/engines/__tests__/handlebars.engine.spec.ts
··· 10 10 describe("HandlebarsEngine helpers", () => { 11 11 describe("hasItems", () => { 12 12 it("returns true for non-empty arrays", () => { 13 - expect(render("{{#if (hasItems xs)}}YES{{else}}NO{{/if}}", { xs: [1] })).toBe( 14 - "YES", 15 - ); 13 + expect( 14 + render("{{#if (hasItems xs)}}YES{{else}}NO{{/if}}", { xs: [1] }), 15 + ).toBe("YES"); 16 16 }); 17 17 18 18 it("returns false for empty arrays, null, undefined, non-arrays", () => { 19 - expect(render("{{#if (hasItems xs)}}YES{{else}}NO{{/if}}", { xs: [] })).toBe( 20 - "NO", 21 - ); 19 + expect( 20 + render("{{#if (hasItems xs)}}YES{{else}}NO{{/if}}", { xs: [] }), 21 + ).toBe("NO"); 22 22 expect( 23 23 render("{{#if (hasItems xs)}}YES{{else}}NO{{/if}}", { xs: null }), 24 24 ).toBe("NO"); 25 - expect(render("{{#if (hasItems xs)}}YES{{else}}NO{{/if}}", {})).toBe("NO"); 25 + expect(render("{{#if (hasItems xs)}}YES{{else}}NO{{/if}}", {})).toBe( 26 + "NO", 27 + ); 26 28 expect( 27 29 render("{{#if (hasItems xs)}}YES{{else}}NO{{/if}}", { xs: "string" }), 28 30 ).toBe("NO"); ··· 48 50 49 51 describe("eq", () => { 50 52 it("returns true for strict equality", () => { 51 - expect(render("{{#if (eq a b)}}YES{{else}}NO{{/if}}", { a: 1, b: 1 })).toBe( 52 - "YES", 53 - ); 53 + expect( 54 + render("{{#if (eq a b)}}YES{{else}}NO{{/if}}", { a: 1, b: 1 }), 55 + ).toBe("YES"); 54 56 expect( 55 57 render("{{#if (eq a b)}}YES{{else}}NO{{/if}}", { a: "x", b: "x" }), 56 58 ).toBe("YES"); 57 59 }); 58 60 59 61 it("returns false for non-equality", () => { 60 - expect(render("{{#if (eq a b)}}YES{{else}}NO{{/if}}", { a: 1, b: 2 })).toBe( 61 - "NO", 62 - ); 62 + expect( 63 + render("{{#if (eq a b)}}YES{{else}}NO{{/if}}", { a: 1, b: 2 }), 64 + ).toBe("NO"); 63 65 expect( 64 66 render("{{#if (eq a b)}}YES{{else}}NO{{/if}}", { a: "1", b: 1 }), 65 67 ).toBe("NO"); ··· 120 122 }); 121 123 122 124 describe("markdown", () => { 123 - const md = (input: unknown): string => render("{{{markdown x}}}", { x: input }); 125 + const md = (input: unknown): string => 126 + render("{{{markdown x}}}", { x: input }); 124 127 125 128 it("returns empty string for empty input", () => { 126 129 expect(md("")).toBe("");
+3 -3
packages/cv-renderer/src/index.ts
··· 1 - export type { TemplateEngine } from "./template-engine.interface"; 2 - export type { CVRenderContext } from "./render-context"; 3 - export { HandlebarsEngine } from "./engines/handlebars.engine"; 4 1 export { wrapInDocument } from "./document"; 2 + export { HandlebarsEngine } from "./engines/handlebars.engine"; 3 + export type { CVRenderContext } from "./render-context"; 4 + export type { TemplateEngine } from "./template-engine.interface";
+2 -2
packages/file-storage/src/disk-file-storage.ts
··· 1 1 import * as fs from "node:fs/promises"; 2 2 import * as path from "node:path"; 3 - import { Injectable, Logger } from "@nestjs/common"; 4 3 import { raise } from "@cv/utils"; 5 - import type { FileStorage } from "./file-storage.interface"; 4 + import { Injectable, Logger } from "@nestjs/common"; 5 + import { FileStorage } from "./file-storage.interface"; 6 6 7 7 @Injectable() 8 8 export class DiskFileStorage implements FileStorage {
+2 -2
packages/file-storage/src/file-storage.module.ts
··· 1 - import { type DynamicModule, Module, type Type } from "@nestjs/common"; 1 + import { DynamicModule, Module, type Type } from "@nestjs/common"; 2 2 import { DiskFileStorage } from "./disk-file-storage"; 3 3 import { FILE_STORAGE } from "./file-storage.interface"; 4 - import { R2FileStorage, type R2FileStorageConfig } from "./r2-file-storage"; 4 + import { R2FileStorage, R2FileStorageConfig } from "./r2-file-storage"; 5 5 6 6 type DiskConfig = { driver: "disk"; baseDir: string }; 7 7 type R2Config = { driver: "r2" } & R2FileStorageConfig;
+1 -1
packages/file-storage/src/r2-file-storage.ts
··· 6 6 S3Client, 7 7 } from "@aws-sdk/client-s3"; 8 8 import { Injectable, Logger } from "@nestjs/common"; 9 - import type { FileStorage } from "./file-storage.interface"; 9 + import { FileStorage } from "./file-storage.interface"; 10 10 11 11 export type R2FileStorageConfig = { 12 12 endpoint: string;
+1 -3
packages/file-upload/src/__tests__/validators.spec.ts
··· 9 9 ]); 10 10 const utf8Bytes = Buffer.from("hello world\n"); 11 11 const invalidUtf8 = Buffer.from([0xff, 0xfe, 0xfd]); 12 - const pngBytes = Buffer.from([ 13 - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 14 - ]); 12 + const pngBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); 15 13 const empty = Buffer.alloc(0); 16 14 17 15 describe("validateMagicBytes", () => {
+1 -1
packages/file-upload/src/extractor-registry.ts
··· 1 1 import { NamedRegistry } from "@cv/utils"; 2 - import type { TextExtractor, TextExtractionResult } from "./types"; 2 + import { TextExtractionResult, TextExtractor } from "./types"; 3 3 4 4 /** 5 5 * Routes file-extraction requests to the registered extractor that supports
+10 -8
packages/file-upload/src/extractors/docx.extractor.ts
··· 1 - import * as mammoth from 'mammoth'; 2 - import type { TextExtractor, TextExtractionResult } from '../types'; 3 - import { withTimeout } from './with-timeout'; 1 + import * as mammoth from "mammoth"; 2 + import { TextExtractionResult, TextExtractor } from "../types"; 3 + import { withTimeout } from "./with-timeout"; 4 4 5 5 /** 6 6 * Text extractor for DOCX files. Wrapped in withTimeout against malformed 7 7 * docx hangs (CVG-57; see CVG-60 for decompressed-size / zip-bomb cap). 8 8 */ 9 9 export class DOCXExtractor implements TextExtractor { 10 - readonly name = 'docx'; 10 + readonly name = "docx"; 11 11 readonly supportedMimeTypes = [ 12 - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 12 + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 13 13 ] as const; 14 14 15 15 canHandle(mimeType: string): boolean { 16 - return this.supportedMimeTypes.includes(mimeType as typeof this.supportedMimeTypes[number]); 16 + return this.supportedMimeTypes.includes( 17 + mimeType as (typeof this.supportedMimeTypes)[number], 18 + ); 17 19 } 18 20 19 21 async extract(buffer: Buffer): Promise<TextExtractionResult> { 20 - return withTimeout(this.run(buffer), 'docx'); 22 + return withTimeout(this.run(buffer), "docx"); 21 23 } 22 24 23 25 private async run(buffer: Buffer): Promise<TextExtractionResult> { ··· 27 29 } catch (error) { 28 30 return { 29 31 success: false, 30 - error: `DOCX extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 32 + error: `DOCX extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`, 31 33 }; 32 34 } 33 35 }
+84 -45
packages/file-upload/src/extractors/pdf/__tests__/pdf-extractor.spec.ts
··· 1 - import type { Mock } from 'vitest'; 2 - import type { TextExtractionResult } from '../../../types'; 3 - import type { PdfExtractionStrategy } from '../types'; 4 - import { PDFExtractor } from '../pdf.extractor'; 1 + import { Mock } from "vitest"; 2 + import { TextExtractionResult } from "../../../types"; 3 + import { PDFExtractor } from "../pdf.extractor"; 4 + import { PdfExtractionStrategy } from "../types"; 5 5 6 6 const makeStrategy = ( 7 7 name: string, ··· 13 13 extract: vi.fn().mockResolvedValue(result), 14 14 }); 15 15 16 - const buf = Buffer.from('fake-pdf'); 16 + const buf = Buffer.from("fake-pdf"); 17 17 18 - describe('PDFExtractor (composite)', () => { 19 - it('picks the first strategy that meets the threshold', async () => { 20 - const stratA = makeStrategy('a', 0, { success: true, text: 'x'.repeat(100) }); 21 - const stratB = makeStrategy('b', 1, { success: true, text: 'y'.repeat(200) }); 18 + describe("PDFExtractor (composite)", () => { 19 + it("picks the first strategy that meets the threshold", async () => { 20 + const stratA = makeStrategy("a", 0, { 21 + success: true, 22 + text: "x".repeat(100), 23 + }); 24 + const stratB = makeStrategy("b", 1, { 25 + success: true, 26 + text: "y".repeat(200), 27 + }); 22 28 23 29 const extractor = new PDFExtractor({ 24 30 strategies: [stratA, stratB], ··· 27 33 28 34 const result = await extractor.extract(buf); 29 35 30 - expect(result).toEqual({ success: true, text: 'x'.repeat(100) }); 36 + expect(result).toEqual({ success: true, text: "x".repeat(100) }); 31 37 expect(stratA.extract).toHaveBeenCalledWith(buf); 32 38 expect(stratB.extract).not.toHaveBeenCalled(); 33 - expect(extractor.lastUsedStrategy).toBe('a'); 39 + expect(extractor.lastUsedStrategy).toBe("a"); 34 40 }); 35 41 36 - it('skips a strategy below threshold and picks the next one that meets it', async () => { 37 - const stratA = makeStrategy('a', 0, { success: true, text: 'x'.repeat(10) }); 38 - const stratB = makeStrategy('b', 1, { success: true, text: 'y'.repeat(200) }); 42 + it("skips a strategy below threshold and picks the next one that meets it", async () => { 43 + const stratA = makeStrategy("a", 0, { 44 + success: true, 45 + text: "x".repeat(10), 46 + }); 47 + const stratB = makeStrategy("b", 1, { 48 + success: true, 49 + text: "y".repeat(200), 50 + }); 39 51 40 52 const extractor = new PDFExtractor({ 41 53 strategies: [stratA, stratB], ··· 44 56 45 57 const result = await extractor.extract(buf); 46 58 47 - expect(result).toEqual({ success: true, text: 'y'.repeat(200) }); 48 - expect(extractor.lastUsedStrategy).toBe('b'); 59 + expect(result).toEqual({ success: true, text: "y".repeat(200) }); 60 + expect(extractor.lastUsedStrategy).toBe("b"); 49 61 }); 50 62 51 - it('falls back to the longest non-empty result when nothing meets threshold', async () => { 52 - const stratA = makeStrategy('short', 0, { success: true, text: 'x'.repeat(10) }); 53 - const stratB = makeStrategy('longer', 1, { success: true, text: 'y'.repeat(30) }); 54 - const stratC = makeStrategy('medium', 2, { success: true, text: 'z'.repeat(20) }); 63 + it("falls back to the longest non-empty result when nothing meets threshold", async () => { 64 + const stratA = makeStrategy("short", 0, { 65 + success: true, 66 + text: "x".repeat(10), 67 + }); 68 + const stratB = makeStrategy("longer", 1, { 69 + success: true, 70 + text: "y".repeat(30), 71 + }); 72 + const stratC = makeStrategy("medium", 2, { 73 + success: true, 74 + text: "z".repeat(20), 75 + }); 55 76 56 77 const extractor = new PDFExtractor({ 57 78 strategies: [stratA, stratB, stratC], ··· 60 81 61 82 const result = await extractor.extract(buf); 62 83 63 - expect(result).toEqual({ success: true, text: 'y'.repeat(30) }); 64 - expect(extractor.lastUsedStrategy).toBe('longer'); 84 + expect(result).toEqual({ success: true, text: "y".repeat(30) }); 85 + expect(extractor.lastUsedStrategy).toBe("longer"); 65 86 }); 66 87 67 - it('returns failure when all strategies fail', async () => { 68 - const stratA = makeStrategy('a', 0, { success: false, error: 'boom' }); 69 - const stratB = makeStrategy('b', 1, { success: false, error: 'bang' }); 88 + it("returns failure when all strategies fail", async () => { 89 + const stratA = makeStrategy("a", 0, { success: false, error: "boom" }); 90 + const stratB = makeStrategy("b", 1, { success: false, error: "bang" }); 70 91 71 92 const extractor = new PDFExtractor({ strategies: [stratA, stratB] }); 72 93 73 94 const result = await extractor.extract(buf); 74 95 75 - expect(result).toEqual({ success: false, error: 'All PDF extraction strategies failed' }); 96 + expect(result).toEqual({ 97 + success: false, 98 + error: "All PDF extraction strategies failed", 99 + }); 76 100 expect(extractor.lastUsedStrategy).toBeUndefined(); 77 101 }); 78 102 79 - it('returns failure with empty strategies array', async () => { 103 + it("returns failure with empty strategies array", async () => { 80 104 const extractor = new PDFExtractor({ strategies: [] }); 81 105 82 106 const result = await extractor.extract(buf); 83 107 84 - expect(result).toEqual({ success: false, error: 'No PDF extraction strategies configured' }); 108 + expect(result).toEqual({ 109 + success: false, 110 + error: "No PDF extraction strategies configured", 111 + }); 85 112 }); 86 113 87 - it('respects custom threshold', async () => { 88 - const strat = makeStrategy('a', 0, { success: true, text: 'x'.repeat(5) }); 114 + it("respects custom threshold", async () => { 115 + const strat = makeStrategy("a", 0, { success: true, text: "x".repeat(5) }); 89 116 90 117 const extractor = new PDFExtractor({ 91 118 strategies: [strat], ··· 94 121 95 122 const result = await extractor.extract(buf); 96 123 97 - expect(result).toEqual({ success: true, text: 'x'.repeat(5) }); 98 - expect(extractor.lastUsedStrategy).toBe('a'); 124 + expect(result).toEqual({ success: true, text: "x".repeat(5) }); 125 + expect(extractor.lastUsedStrategy).toBe("a"); 99 126 }); 100 127 101 - it('sorts strategies by priority regardless of insertion order', async () => { 102 - const stratHigh = makeStrategy('high', 10, { success: true, text: 'x'.repeat(100) }); 103 - const stratLow = makeStrategy('low', 0, { success: true, text: 'y'.repeat(100) }); 128 + it("sorts strategies by priority regardless of insertion order", async () => { 129 + const stratHigh = makeStrategy("high", 10, { 130 + success: true, 131 + text: "x".repeat(100), 132 + }); 133 + const stratLow = makeStrategy("low", 0, { 134 + success: true, 135 + text: "y".repeat(100), 136 + }); 104 137 105 138 const extractor = new PDFExtractor({ 106 139 strategies: [stratHigh, stratLow], ··· 109 142 110 143 const result = await extractor.extract(buf); 111 144 112 - expect(result).toEqual({ success: true, text: 'y'.repeat(100) }); 113 - expect(extractor.lastUsedStrategy).toBe('low'); 145 + expect(result).toEqual({ success: true, text: "y".repeat(100) }); 146 + expect(extractor.lastUsedStrategy).toBe("low"); 114 147 expect(stratHigh.extract).not.toHaveBeenCalled(); 115 148 }); 116 149 117 - it('skips failed strategies and continues to the next', async () => { 118 - const stratFail = makeStrategy('fail', 0, { success: false, error: 'oops' }); 119 - const stratOk = makeStrategy('ok', 1, { success: true, text: 'x'.repeat(100) }); 150 + it("skips failed strategies and continues to the next", async () => { 151 + const stratFail = makeStrategy("fail", 0, { 152 + success: false, 153 + error: "oops", 154 + }); 155 + const stratOk = makeStrategy("ok", 1, { 156 + success: true, 157 + text: "x".repeat(100), 158 + }); 120 159 121 160 const extractor = new PDFExtractor({ 122 161 strategies: [stratFail, stratOk], ··· 125 164 126 165 const result = await extractor.extract(buf); 127 166 128 - expect(result).toEqual({ success: true, text: 'x'.repeat(100) }); 129 - expect(extractor.lastUsedStrategy).toBe('ok'); 167 + expect(result).toEqual({ success: true, text: "x".repeat(100) }); 168 + expect(extractor.lastUsedStrategy).toBe("ok"); 130 169 }); 131 170 132 - it('reports canHandle correctly', () => { 171 + it("reports canHandle correctly", () => { 133 172 const extractor = new PDFExtractor({ strategies: [] }); 134 173 135 - expect(extractor.canHandle('application/pdf')).toBe(true); 136 - expect(extractor.canHandle('text/plain')).toBe(false); 174 + expect(extractor.canHandle("application/pdf")).toBe(true); 175 + expect(extractor.canHandle("text/plain")).toBe(false); 137 176 }); 138 177 });
+21 -21
packages/file-upload/src/extractors/pdf/__tests__/strategies.spec.ts
··· 1 - import { PdfParseStrategy } from '../strategies/pdf-parse.strategy'; 2 - import { Pdf2JsonStrategy } from '../strategies/pdf2json.strategy'; 3 - import { TesseractOcrStrategy } from '../strategies/tesseract-ocr.strategy'; 1 + import { PdfParseStrategy } from "../strategies/pdf-parse.strategy"; 2 + import { Pdf2JsonStrategy } from "../strategies/pdf2json.strategy"; 3 + import { TesseractOcrStrategy } from "../strategies/tesseract-ocr.strategy"; 4 4 5 - describe('PdfParseStrategy', () => { 5 + describe("PdfParseStrategy", () => { 6 6 const strategy = new PdfParseStrategy(); 7 7 8 - it('has correct name and priority', () => { 9 - expect(strategy.name).toBe('pdf-parse'); 8 + it("has correct name and priority", () => { 9 + expect(strategy.name).toBe("pdf-parse"); 10 10 expect(strategy.priority).toBe(0); 11 11 }); 12 12 13 - it('returns failure on invalid buffer', async () => { 14 - const result = await strategy.extract(Buffer.from('not-a-pdf')); 13 + it("returns failure on invalid buffer", async () => { 14 + const result = await strategy.extract(Buffer.from("not-a-pdf")); 15 15 16 16 expect(result.success).toBe(false); 17 17 if (!result.success) { 18 - expect(result.error).toContain('pdf-parse failed'); 18 + expect(result.error).toContain("pdf-parse failed"); 19 19 } 20 20 }); 21 21 }); 22 22 23 - describe('Pdf2JsonStrategy', () => { 23 + describe("Pdf2JsonStrategy", () => { 24 24 const strategy = new Pdf2JsonStrategy(); 25 25 26 - it('has correct name and priority', () => { 27 - expect(strategy.name).toBe('pdf2json'); 26 + it("has correct name and priority", () => { 27 + expect(strategy.name).toBe("pdf2json"); 28 28 expect(strategy.priority).toBe(1); 29 29 }); 30 30 31 - it('returns failure on invalid buffer', async () => { 32 - const result = await strategy.extract(Buffer.from('not-a-pdf')); 31 + it("returns failure on invalid buffer", async () => { 32 + const result = await strategy.extract(Buffer.from("not-a-pdf")); 33 33 34 34 expect(result.success).toBe(false); 35 35 if (!result.success) { 36 - expect(result.error).toContain('pdf2json failed'); 36 + expect(result.error).toContain("pdf2json failed"); 37 37 } 38 38 }); 39 39 }); 40 40 41 - describe('TesseractOcrStrategy', () => { 41 + describe("TesseractOcrStrategy", () => { 42 42 const strategy = new TesseractOcrStrategy(); 43 43 44 - it('has correct name and priority', () => { 45 - expect(strategy.name).toBe('tesseract-ocr'); 44 + it("has correct name and priority", () => { 45 + expect(strategy.name).toBe("tesseract-ocr"); 46 46 expect(strategy.priority).toBe(2); 47 47 }); 48 48 49 - it('returns failure on invalid buffer', async () => { 50 - const result = await strategy.extract(Buffer.from('not-a-pdf')); 49 + it("returns failure on invalid buffer", async () => { 50 + const result = await strategy.extract(Buffer.from("not-a-pdf")); 51 51 52 52 expect(result.success).toBe(false); 53 53 if (!result.success) { 54 - expect(result.error).toContain('tesseract-ocr failed'); 54 + expect(result.error).toContain("tesseract-ocr failed"); 55 55 } 56 56 }); 57 57 });
+27 -15
packages/file-upload/src/extractors/pdf/pdf.extractor.ts
··· 1 - import { Logger } from '@nestjs/common'; 2 - import type { TextExtractor, TextExtractionResult } from '../../types'; 3 - import type { PdfExtractionStrategy, PdfExtractorConfig } from './types'; 4 - import { PdfParseStrategy } from './strategies/pdf-parse.strategy'; 5 - import { Pdf2JsonStrategy } from './strategies/pdf2json.strategy'; 6 - import { TesseractOcrStrategy } from './strategies/tesseract-ocr.strategy'; 1 + import { Logger } from "@nestjs/common"; 2 + import { TextExtractionResult, TextExtractor } from "../../types"; 3 + import { PdfParseStrategy } from "./strategies/pdf-parse.strategy"; 4 + import { Pdf2JsonStrategy } from "./strategies/pdf2json.strategy"; 5 + import { TesseractOcrStrategy } from "./strategies/tesseract-ocr.strategy"; 6 + import { PdfExtractionStrategy, PdfExtractorConfig } from "./types"; 7 7 8 8 const DEFAULT_MIN_CHAR_THRESHOLD = 50; 9 9 ··· 19 19 * meeting the minimum character threshold. 20 20 */ 21 21 export class PDFExtractor implements TextExtractor { 22 - readonly name = 'pdf'; 23 - readonly supportedMimeTypes = ['application/pdf'] as const; 22 + readonly name = "pdf"; 23 + readonly supportedMimeTypes = ["application/pdf"] as const; 24 24 25 25 private readonly strategies: PdfExtractionStrategy[]; 26 26 private readonly minCharThreshold: number; ··· 32 32 constructor(config: PdfExtractorConfig = {}) { 33 33 const raw = config.strategies ?? defaultStrategies(); 34 34 this.strategies = [...raw].sort((a, b) => a.priority - b.priority); 35 - this.minCharThreshold = config.minCharThreshold ?? DEFAULT_MIN_CHAR_THRESHOLD; 35 + this.minCharThreshold = 36 + config.minCharThreshold ?? DEFAULT_MIN_CHAR_THRESHOLD; 36 37 } 37 38 38 39 canHandle(mimeType: string): boolean { 39 - return this.supportedMimeTypes.includes(mimeType as typeof this.supportedMimeTypes[number]); 40 + return this.supportedMimeTypes.includes( 41 + mimeType as (typeof this.supportedMimeTypes)[number], 42 + ); 40 43 } 41 44 42 45 async extract(buffer: Buffer): Promise<TextExtractionResult> { 43 46 if (this.strategies.length === 0) { 44 - return { success: false, error: 'No PDF extraction strategies configured' }; 47 + return { 48 + success: false, 49 + error: "No PDF extraction strategies configured", 50 + }; 45 51 } 46 52 47 53 let bestResult: TextExtractionResult | undefined; ··· 52 58 const result = await strategy.extract(buffer); 53 59 54 60 if (!result.success) { 55 - this.logger.debug(`Strategy '${strategy.name}' failed: ${result.error}`); 61 + this.logger.debug( 62 + `Strategy '${strategy.name}' failed: ${result.error}`, 63 + ); 56 64 continue; 57 65 } 58 66 59 67 const charCount = result.text.trim().length; 60 - this.logger.debug(`Strategy '${strategy.name}' extracted ${charCount} chars`); 68 + this.logger.debug( 69 + `Strategy '${strategy.name}' extracted ${charCount} chars`, 70 + ); 61 71 62 72 if (charCount >= this.minCharThreshold) { 63 - this.logger.log(`Selected strategy '${strategy.name}' (${charCount} chars)`); 73 + this.logger.log( 74 + `Selected strategy '${strategy.name}' (${charCount} chars)`, 75 + ); 64 76 this.lastUsedStrategy = strategy.name; 65 77 return result; 66 78 } ··· 81 93 } 82 94 83 95 this.lastUsedStrategy = undefined; 84 - return { success: false, error: 'All PDF extraction strategies failed' }; 96 + return { success: false, error: "All PDF extraction strategies failed" }; 85 97 } 86 98 }
+9 -7
packages/file-upload/src/extractors/pdf/strategies/pdf-parse.strategy.ts
··· 1 - import type { TextExtractionResult } from '../../../types'; 2 - import type { PdfExtractionStrategy } from '../types'; 3 - import { withTimeout } from '../../with-timeout'; 1 + import { TextExtractionResult } from "../../../types"; 2 + import { withTimeout } from "../../with-timeout"; 3 + import { PdfExtractionStrategy } from "../types"; 4 4 5 5 // eslint-disable-next-line @typescript-eslint/no-require-imports 6 - const pdfParse = require('pdf-parse') as (buffer: Buffer) => Promise<{ text: string }>; 6 + const pdfParse = require("pdf-parse") as ( 7 + buffer: Buffer, 8 + ) => Promise<{ text: string }>; 7 9 8 10 /** 9 11 * Fast text extraction using pdf-parse. ··· 13 15 * which would cascade into request timeouts and worker starvation. CVG-57. 14 16 */ 15 17 export class PdfParseStrategy implements PdfExtractionStrategy { 16 - readonly name = 'pdf-parse'; 18 + readonly name = "pdf-parse"; 17 19 readonly priority = 0; 18 20 19 21 async extract(buffer: Buffer): Promise<TextExtractionResult> { 20 - return withTimeout(this.run(buffer), 'pdf-parse'); 22 + return withTimeout(this.run(buffer), "pdf-parse"); 21 23 } 22 24 23 25 private async run(buffer: Buffer): Promise<TextExtractionResult> { ··· 27 29 } catch (error) { 28 30 return { 29 31 success: false, 30 - error: `pdf-parse failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 32 + error: `pdf-parse failed: ${error instanceof Error ? error.message : "Unknown error"}`, 31 33 }; 32 34 } 33 35 }
+15 -15
packages/file-upload/src/extractors/pdf/strategies/pdf2json.strategy.ts
··· 1 - import type { TextExtractionResult } from '../../../types'; 2 - import type { PdfExtractionStrategy } from '../types'; 3 - import { withTimeout } from '../../with-timeout'; 1 + import { TextExtractionResult } from "../../../types"; 2 + import { withTimeout } from "../../with-timeout"; 3 + import { PdfExtractionStrategy } from "../types"; 4 4 5 5 /** 6 6 * Text extraction using pdf2json. ··· 9 9 * Wrapped in withTimeout against malformed-PDF hangs (CVG-57). 10 10 */ 11 11 export class Pdf2JsonStrategy implements PdfExtractionStrategy { 12 - readonly name = 'pdf2json'; 12 + readonly name = "pdf2json"; 13 13 readonly priority = 1; 14 14 15 15 async extract(buffer: Buffer): Promise<TextExtractionResult> { 16 - return withTimeout(this.run(buffer), 'pdf2json'); 16 + return withTimeout(this.run(buffer), "pdf2json"); 17 17 } 18 18 19 19 private async run(buffer: Buffer): Promise<TextExtractionResult> { 20 20 try { 21 - const { default: PDFParser } = await import('pdf2json'); 21 + const { default: PDFParser } = await import("pdf2json"); 22 22 const text = await this.parseBuffer(PDFParser, buffer); 23 23 return { success: true, text }; 24 24 } catch (error) { 25 25 return { 26 26 success: false, 27 - error: `pdf2json failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 27 + error: `pdf2json failed: ${error instanceof Error ? error.message : "Unknown error"}`, 28 28 }; 29 29 } 30 30 } 31 31 32 32 private parseBuffer( 33 - PDFParser: typeof import('pdf2json').default, 33 + PDFParser: typeof import("pdf2json").default, 34 34 buffer: Buffer, 35 35 ): Promise<string> { 36 36 return new Promise((resolve, reject) => { 37 37 const parser = new PDFParser(null, true); 38 38 39 - parser.on('pdfParser_dataReady', (pdfData) => { 39 + parser.on("pdfParser_dataReady", (pdfData) => { 40 40 try { 41 41 const text = pdfData.Pages.map((page) => 42 42 page.Texts.map((textItem) => 43 - textItem.R.map((run) => decodeURIComponent(run.T)).join(''), 44 - ).join(' '), 45 - ).join('\n'); 43 + textItem.R.map((run) => decodeURIComponent(run.T)).join(""), 44 + ).join(" "), 45 + ).join("\n"); 46 46 47 47 resolve(text); 48 48 } catch (err) { ··· 50 50 } 51 51 }); 52 52 53 - parser.on('pdfParser_dataError', (errData) => { 53 + parser.on("pdfParser_dataError", (errData) => { 54 54 const message = 55 55 errData instanceof Error 56 56 ? errData.message 57 - : 'parserError' in errData 57 + : "parserError" in errData 58 58 ? String(errData.parserError) 59 - : 'pdf2json parse error'; 59 + : "pdf2json parse error"; 60 60 reject(new Error(message)); 61 61 }); 62 62
+18 -14
packages/file-upload/src/extractors/pdf/strategies/tesseract-ocr.strategy.ts
··· 1 - import type { TextExtractionResult } from '../../../types'; 2 - import type { PdfExtractionStrategy } from '../types'; 3 - import { withTimeout } from '../../with-timeout'; 1 + import { TextExtractionResult } from "../../../types"; 2 + import { withTimeout } from "../../with-timeout"; 3 + import { PdfExtractionStrategy } from "../types"; 4 4 5 5 const OCR_TIMEOUT_MS = 60_000; 6 6 ··· 13 13 * on multi-page scans. Still bounded to avoid worker starvation. 14 14 */ 15 15 export class TesseractOcrStrategy implements PdfExtractionStrategy { 16 - readonly name = 'tesseract-ocr'; 16 + readonly name = "tesseract-ocr"; 17 17 readonly priority = 2; 18 18 19 19 async extract(buffer: Buffer): Promise<TextExtractionResult> { 20 - return withTimeout(this.run(buffer), 'tesseract-ocr', OCR_TIMEOUT_MS); 20 + return withTimeout(this.run(buffer), "tesseract-ocr", OCR_TIMEOUT_MS); 21 21 } 22 22 23 23 private async run(buffer: Buffer): Promise<TextExtractionResult> { ··· 25 25 const pageImages = await this.renderPagesToImages(buffer); 26 26 27 27 if (pageImages.length === 0) { 28 - return { success: false, error: 'tesseract-ocr: no pages rendered from PDF' }; 28 + return { 29 + success: false, 30 + error: "tesseract-ocr: no pages rendered from PDF", 31 + }; 29 32 } 30 33 31 34 const text = await this.ocrImages(pageImages); ··· 33 36 } catch (error) { 34 37 return { 35 38 success: false, 36 - error: `tesseract-ocr failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 39 + error: `tesseract-ocr failed: ${error instanceof Error ? error.message : "Unknown error"}`, 37 40 }; 38 41 } 39 42 } 40 43 41 44 private async renderPagesToImages(buffer: Buffer): Promise<Buffer[]> { 42 45 // eslint-disable-next-line @typescript-eslint/no-require-imports 43 - const pdfjsLib = require('pdfjs-dist/legacy/build/pdf.js') as typeof import('pdfjs-dist'); 44 - const { createCanvas } = await import('canvas'); 46 + const pdfjsLib = 47 + require("pdfjs-dist/legacy/build/pdf.js") as typeof import("pdfjs-dist"); 48 + const { createCanvas } = await import("canvas"); 45 49 46 50 const data = new Uint8Array(buffer); 47 51 const doc = await pdfjsLib.getDocument({ data }).promise; ··· 52 56 const viewport = page.getViewport({ scale: 2.0 }); 53 57 54 58 const canvas = createCanvas(viewport.width, viewport.height); 55 - const context = canvas.getContext('2d'); 59 + const context = canvas.getContext("2d"); 56 60 57 61 await page.render({ 58 62 canvasContext: context as unknown as CanvasRenderingContext2D, 59 63 viewport, 60 64 }).promise; 61 65 62 - images.push(canvas.toBuffer('image/png')); 66 + images.push(canvas.toBuffer("image/png")); 63 67 } 64 68 65 69 return images; 66 70 } 67 71 68 72 private async ocrImages(images: Buffer[]): Promise<string> { 69 - const Tesseract = await import('tesseract.js'); 70 - const worker = await Tesseract.createWorker('eng'); 73 + const Tesseract = await import("tesseract.js"); 74 + const worker = await Tesseract.createWorker("eng"); 71 75 72 76 try { 73 77 const pages: string[] = []; ··· 77 81 pages.push(data.text); 78 82 } 79 83 80 - return pages.join('\n'); 84 + return pages.join("\n"); 81 85 } finally { 82 86 await worker.terminate(); 83 87 }
+1 -1
packages/file-upload/src/extractors/pdf/types.ts
··· 1 - import type { TextExtractionResult } from '../../types'; 1 + import { TextExtractionResult } from "../../types"; 2 2 3 3 /** 4 4 * Strategy for extracting text from a PDF buffer.
+8 -6
packages/file-upload/src/extractors/plain-text.extractor.ts
··· 1 - import type { TextExtractor, TextExtractionResult } from '../types'; 1 + import { TextExtractionResult, TextExtractor } from "../types"; 2 2 3 3 /** 4 4 * Text extractor for plain text files (TXT, MD) 5 5 */ 6 6 export class PlainTextExtractor implements TextExtractor { 7 - readonly name = 'plain-text'; 8 - readonly supportedMimeTypes = ['text/plain', 'text/markdown'] as const; 7 + readonly name = "plain-text"; 8 + readonly supportedMimeTypes = ["text/plain", "text/markdown"] as const; 9 9 10 10 canHandle(mimeType: string): boolean { 11 - return this.supportedMimeTypes.includes(mimeType as typeof this.supportedMimeTypes[number]); 11 + return this.supportedMimeTypes.includes( 12 + mimeType as (typeof this.supportedMimeTypes)[number], 13 + ); 12 14 } 13 15 14 16 async extract(buffer: Buffer): Promise<TextExtractionResult> { 15 17 try { 16 - const text = buffer.toString('utf-8'); 18 + const text = buffer.toString("utf-8"); 17 19 return { success: true, text }; 18 20 } catch (error) { 19 21 return { 20 22 success: false, 21 - error: `Text extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 23 + error: `Text extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`, 22 24 }; 23 25 } 24 26 }
+6 -6
packages/file-upload/src/extractors/with-timeout.ts
··· 11 11 * 12 12 * Used by every extractor that parses user-supplied buffers. 13 13 */ 14 - import type { TextExtractionResult } from "../types"; 14 + import { TextExtractionResult } from "../types"; 15 15 16 16 export const DEFAULT_PARSE_TIMEOUT_MS = 15_000; 17 17 18 - const timeoutResult = ( 19 - taskName: string, 20 - ms: number, 21 - ): TextExtractionResult => ({ 18 + const timeoutResult = (taskName: string, ms: number): TextExtractionResult => ({ 22 19 success: false, 23 20 error: `${taskName} exceeded ${ms}ms timeout`, 24 21 }); ··· 30 27 ): Promise<TextExtractionResult> => { 31 28 let timer: NodeJS.Timeout | undefined; 32 29 const timeout = new Promise<TextExtractionResult>((resolve) => { 33 - timer = setTimeout(() => resolve(timeoutResult(taskName, timeoutMs)), timeoutMs); 30 + timer = setTimeout( 31 + () => resolve(timeoutResult(taskName, timeoutMs)), 32 + timeoutMs, 33 + ); 34 34 }); 35 35 try { 36 36 return await Promise.race([task, timeout]);
+10 -8
packages/file-upload/src/file-extraction.module.ts
··· 1 - import { DynamicModule, Module } from '@nestjs/common'; 2 - import type { TextExtractor } from './types'; 3 - import { TextExtractorRegistry } from './extractor-registry'; 4 - import { PDFExtractor } from './extractors/pdf/pdf.extractor'; 5 - import { DOCXExtractor } from './extractors/docx.extractor'; 6 - import { PlainTextExtractor } from './extractors/plain-text.extractor'; 1 + import { DynamicModule, Module } from "@nestjs/common"; 2 + import { TextExtractorRegistry } from "./extractor-registry"; 3 + import { DOCXExtractor } from "./extractors/docx.extractor"; 4 + import { PDFExtractor } from "./extractors/pdf/pdf.extractor"; 5 + import { PlainTextExtractor } from "./extractors/plain-text.extractor"; 6 + import { TextExtractor } from "./types"; 7 7 8 - export const TEXT_EXTRACTOR_REGISTRY = Symbol('TEXT_EXTRACTOR_REGISTRY'); 8 + export const TEXT_EXTRACTOR_REGISTRY = Symbol("TEXT_EXTRACTOR_REGISTRY"); 9 9 10 10 export interface FileExtractionModuleOptions { 11 11 extractors?: TextExtractor[]; ··· 30 30 .register(new PlainTextExtractor()); 31 31 } 32 32 33 - options.extractors?.forEach((extractor) => registry.register(extractor)); 33 + options.extractors?.forEach((extractor) => 34 + registry.register(extractor), 35 + ); 34 36 35 37 return registry; 36 38 },
+26 -26
packages/file-upload/src/index.ts
··· 1 1 // Types and schemas 2 + 3 + // Extractor registry 4 + export { TextExtractorRegistry } from "./extractor-registry"; 5 + export { DOCXExtractor } from "./extractors/docx.extractor"; 6 + 7 + // Extractor implementations 8 + export { PDFExtractor } from "./extractors/pdf/pdf.extractor"; 9 + // PDF strategy types (for extensibility) 2 10 export type { 3 - TextExtractor, 4 - TextExtractionResult, 5 - FileValidationResult, 11 + PdfExtractionStrategy, 12 + PdfExtractorConfig, 13 + } from "./extractors/pdf/types"; 14 + export { PlainTextExtractor } from "./extractors/plain-text.extractor"; 15 + // NestJS Module 16 + export { 17 + FileExtractionModule, 18 + type FileExtractionModuleOptions, 19 + TEXT_EXTRACTOR_REGISTRY, 20 + } from "./file-extraction.module"; 21 + export type { 6 22 FileUpload, 7 23 FileUploadInput, 24 + FileValidationResult, 8 25 SupportedMimeType, 9 - } from './types'; 10 - export { SupportedMimeTypes, FileUploadSchema } from './types'; 11 - 12 - // Extractor implementations 13 - export { PDFExtractor } from './extractors/pdf/pdf.extractor'; 14 - export { DOCXExtractor } from './extractors/docx.extractor'; 15 - export { PlainTextExtractor } from './extractors/plain-text.extractor'; 16 - 17 - // PDF strategy types (for extensibility) 18 - export type { PdfExtractionStrategy, PdfExtractorConfig } from './extractors/pdf/types'; 19 - 20 - // Extractor registry 21 - export { TextExtractorRegistry } from './extractor-registry'; 22 - 26 + TextExtractionResult, 27 + TextExtractor, 28 + } from "./types"; 29 + export { FileUploadSchema, SupportedMimeTypes } from "./types"; 23 30 // File validators 24 31 export { 32 + isSupportedMimeType, 25 33 validateFile, 26 34 validateFileName, 27 35 validateFileSize, 28 36 validateMimeType, 29 - isSupportedMimeType, 30 - } from './validators'; 31 - 32 - // NestJS Module 33 - export { 34 - FileExtractionModule, 35 - TEXT_EXTRACTOR_REGISTRY, 36 - type FileExtractionModuleOptions, 37 - } from './file-extraction.module'; 37 + } from "./validators";
+8 -8
packages/file-upload/src/types.ts
··· 4 4 * Supported MIME types for CV uploads 5 5 */ 6 6 export const SupportedMimeTypes = { 7 - PDF: 'application/pdf', 8 - DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 9 - TXT: 'text/plain', 10 - MD: 'text/markdown', 7 + PDF: "application/pdf", 8 + DOCX: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 9 + TXT: "text/plain", 10 + MD: "text/markdown", 11 11 } as const; 12 12 13 13 export type SupportedMimeType = ··· 19 19 export const FileUploadSchema = z.object({ 20 20 originalName: z 21 21 .string() 22 - .min(1, 'File name is required') 23 - .max(255, 'File name is too long'), 22 + .min(1, "File name is required") 23 + .max(255, "File name is too long"), 24 24 mimeType: z.enum([ 25 25 SupportedMimeTypes.PDF, 26 26 SupportedMimeTypes.DOCX, ··· 29 29 ] as const), 30 30 sizeBytes: z 31 31 .number() 32 - .positive('File size must be positive') 33 - .max(10 * 1024 * 1024, 'File size must not exceed 10MB'), 32 + .positive("File size must be positive") 33 + .max(10 * 1024 * 1024, "File size must not exceed 10MB"), 34 34 buffer: z.instanceof(Buffer), 35 35 }); 36 36
+25 -14
packages/file-upload/src/validators.ts
··· 1 - import type { FileUploadInput, FileValidationResult, SupportedMimeType } from './types'; 2 - import { SupportedMimeTypes, FileUploadSchema } from './types'; 1 + import { 2 + FileUploadInput, 3 + FileValidationResult, 4 + SupportedMimeType, 5 + } from "./types"; 6 + import { FileUploadSchema, SupportedMimeTypes } from "./types"; 3 7 4 8 const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB 5 9 ··· 83 87 if (!rule) { 84 88 return { valid: false, error: `No magic-byte rule for MIME ${mimeType}` }; 85 89 } 86 - return rule.check(buffer) ? { valid: true } : { valid: false, error: rule.error }; 90 + return rule.check(buffer) 91 + ? { valid: true } 92 + : { valid: false, error: rule.error }; 87 93 }; 88 94 89 95 /** ··· 97 103 */ 98 104 export const validateFileSize = (sizeBytes: number): FileValidationResult => { 99 105 if (sizeBytes <= 0) { 100 - return { valid: false, error: 'File size must be positive' }; 106 + return { valid: false, error: "File size must be positive" }; 101 107 } 102 108 103 109 if (sizeBytes > MAX_FILE_SIZE) { ··· 115 121 */ 116 122 export const validateMimeType = (mimeType: string): FileValidationResult => { 117 123 if (!mimeType || mimeType.trim().length === 0) { 118 - return { valid: false, error: 'MIME type is required' }; 124 + return { valid: false, error: "MIME type is required" }; 119 125 } 120 126 121 127 if (!isSupportedMimeType(mimeType)) { 122 128 return { 123 129 valid: false, 124 - error: `Unsupported file type. Supported types: ${Object.values(SupportedMimeTypes).join(', ')}`, 130 + error: `Unsupported file type. Supported types: ${Object.values(SupportedMimeTypes).join(", ")}`, 125 131 }; 126 132 } 127 133 ··· 133 139 */ 134 140 export const validateFileName = (fileName: string): FileValidationResult => { 135 141 if (!fileName || fileName.trim().length === 0) { 136 - return { valid: false, error: 'File name is required' }; 142 + return { valid: false, error: "File name is required" }; 137 143 } 138 144 139 145 if (fileName.length > 255) { 140 - return { valid: false, error: 'File name is too long (max 255 characters)' }; 146 + return { 147 + valid: false, 148 + error: "File name is too long (max 255 characters)", 149 + }; 141 150 } 142 151 143 152 return { valid: true }; ··· 146 155 /** 147 156 * Comprehensive file validation 148 157 */ 149 - export const validateFile = (file: Partial<FileUploadInput>): FileValidationResult => { 158 + export const validateFile = ( 159 + file: Partial<FileUploadInput>, 160 + ): FileValidationResult => { 150 161 if (!file.originalName) { 151 - return { valid: false, error: 'File name is required' }; 162 + return { valid: false, error: "File name is required" }; 152 163 } 153 164 154 165 const nameValidation = validateFileName(file.originalName); ··· 157 168 } 158 169 159 170 if (!file.mimeType) { 160 - return { valid: false, error: 'MIME type is required' }; 171 + return { valid: false, error: "MIME type is required" }; 161 172 } 162 173 163 174 const mimeValidation = validateMimeType(file.mimeType); ··· 166 177 } 167 178 168 179 if (file.sizeBytes === undefined) { 169 - return { valid: false, error: 'File size is required' }; 180 + return { valid: false, error: "File size is required" }; 170 181 } 171 182 172 183 const sizeValidation = validateFileSize(file.sizeBytes); ··· 175 186 } 176 187 177 188 if (!file.buffer) { 178 - return { valid: false, error: 'File buffer is required' }; 189 + return { valid: false, error: "File buffer is required" }; 179 190 } 180 191 181 192 // Magic-byte check: declared MIME has been validated above; now confirm ··· 194 205 } catch (error) { 195 206 return { 196 207 valid: false, 197 - error: error instanceof Error ? error.message : 'Validation failed', 208 + error: error instanceof Error ? error.message : "Validation failed", 198 209 }; 199 210 } 200 211 };
+18 -3
packages/handlebars/src/index.ts
··· 19 19 20 20 export const DEFAULT_SANITIZE_OPTIONS: SanitizeOptions = { 21 21 ALLOWED_TAGS: [ 22 - "p", "br", "strong", "em", "u", "code", "pre", "blockquote", 23 - "ul", "ol", "li", "a", 24 - "h1", "h2", "h3", "h4", "h5", "h6", 22 + "p", 23 + "br", 24 + "strong", 25 + "em", 26 + "u", 27 + "code", 28 + "pre", 29 + "blockquote", 30 + "ul", 31 + "ol", 32 + "li", 33 + "a", 34 + "h1", 35 + "h2", 36 + "h3", 37 + "h4", 38 + "h5", 39 + "h6", 25 40 ], 26 41 ALLOWED_ATTR: ["href", "title"], 27 42 ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[/.#?])/i,
+3 -3
packages/handlers/src/handlers.module.ts
··· 1 - import { type DynamicModule, Module } from "@nestjs/common"; 1 + import { DynamicModule, Module } from "@nestjs/common"; 2 + import { HtmlToPdfService } from "./pdf/html-to-pdf.service"; 2 3 import { 3 4 PDF_SERVICE_CONFIG, 4 - type PdfServiceConfig, 5 + PdfServiceConfig, 5 6 } from "./pdf/pdf-service.config"; 6 - import { HtmlToPdfService } from "./pdf/html-to-pdf.service"; 7 7 import { RenderPdfHandler } from "./render-pdf.handler"; 8 8 9 9 export { PDF_SERVICE_CONFIG };
+4 -1
packages/handlers/src/index.ts
··· 1 1 export { HandlersModule } from "./handlers.module"; 2 + export { 3 + HtmlToPdfService, 4 + type PdfServiceConfig, 5 + } from "./pdf/html-to-pdf.service"; 2 6 export { RenderPdfHandler } from "./render-pdf.handler"; 3 - export { HtmlToPdfService, type PdfServiceConfig } from "./pdf/html-to-pdf.service";
+14 -5
packages/handlers/src/pdf/html-to-pdf.service.ts
··· 1 - import { Inject, Injectable, Logger, OnModuleDestroy } from "@nestjs/common"; 1 + import { 2 + Inject, 3 + Injectable, 4 + Logger, 5 + type OnModuleDestroy, 6 + } from "@nestjs/common"; 2 7 import { chromium } from "playwright"; 3 - import type { Browser } from "playwright-core"; 8 + import { Browser } from "playwright-core"; 4 9 import { 5 10 PDF_SERVICE_CONFIG, 6 - type PdfServiceConfig, 11 + PdfServiceConfig, 7 12 } from "./pdf-service.config"; 8 13 9 14 export type { PdfServiceConfig }; ··· 26 31 return this.browser; 27 32 } 28 33 29 - this.logger.log(`Launching Chromium with args: ${this.config.chromiumArgs.join(" ")}`); 30 - this.browser = await chromium.launch({ args: [...this.config.chromiumArgs] }); 34 + this.logger.log( 35 + `Launching Chromium with args: ${this.config.chromiumArgs.join(" ")}`, 36 + ); 37 + this.browser = await chromium.launch({ 38 + args: [...this.config.chromiumArgs], 39 + }); 31 40 return this.browser; 32 41 } 33 42
+5 -3
packages/handlers/src/render-pdf.handler.ts
··· 1 - import { FILE_STORAGE, type FileStorage } from "@cv/file-storage"; 1 + import { FILE_STORAGE, FileStorage } from "@cv/file-storage"; 2 2 import { Inject, Injectable, Logger } from "@nestjs/common"; 3 - import type { Envelope, Handler } from "@riotbyte-com/project-q-core"; 3 + import { Envelope, Handler } from "@riotbyte-com/project-q-core"; 4 4 import { HandlerTag } from "@riotbyte-com/project-q-nestjs"; 5 5 import { HtmlToPdfService } from "./pdf/html-to-pdf.service"; 6 6 ··· 22 22 23 23 async handle(envelope: Envelope): Promise<void> { 24 24 const { cvId, html, requestedBy } = envelope.message.data as RenderPdfData; 25 - this.logger.log(`Rendering PDF for CV ${cvId} (requested by ${requestedBy})`); 25 + this.logger.log( 26 + `Rendering PDF for CV ${cvId} (requested by ${requestedBy})`, 27 + ); 26 28 27 29 const pdf = await this.pdfService.convert(html); 28 30 const key = `${cvId}.pdf`;
+2 -2
packages/mail/src/mail.module.ts
··· 1 - import { type DynamicModule, Module } from "@nestjs/common"; 2 - import { MAIL_SERVICE_TOKEN, type MailService } from "./mail.service.interface"; 1 + import { DynamicModule, Module } from "@nestjs/common"; 2 + import { MAIL_SERVICE_TOKEN, MailService } from "./mail.service.interface"; 3 3 4 4 export interface MailModuleOptions { 5 5 provider: new (...args: unknown[]) => MailService;
+1 -1
packages/mail/src/providers/console-mail.service.ts
··· 1 1 import { Injectable, Logger } from "@nestjs/common"; 2 - import type { MailService, SendMailOptions } from "../mail.service.interface"; 2 + import { MailService, SendMailOptions } from "../mail.service.interface"; 3 3 4 4 @Injectable() 5 5 export class ConsoleMailService implements MailService {
+1 -1
packages/mail/src/providers/example-mail.service.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 - import type { MailService, SendMailOptions } from "../mail.service.interface"; 2 + import { MailService, SendMailOptions } from "../mail.service.interface"; 3 3 4 4 @Injectable() 5 5 export class ExampleMailService implements MailService {
+1 -5
packages/mail/src/resend/resend-mail.service.ts
··· 1 1 import { Injectable } from "@nestjs/common"; 2 2 import { Resend } from "resend"; 3 - import type { 4 - MailAddress, 5 - MailService, 6 - SendMailOptions, 7 - } from "../mail.service.interface"; 3 + import { MailAddress, MailService, SendMailOptions } from "../mail.service.interface"; 8 4 9 5 @Injectable() 10 6 export class ResendMailService implements MailService {
+1 -3
packages/mail/src/resend/resend.module.ts
··· 1 1 import { Global, Logger, Module } from "@nestjs/common"; 2 2 import { ConfigService } from "@nestjs/config"; 3 3 import { 4 - MAIL_SERVICE_TOKEN, 5 - type MailService, 6 - } from "../mail.service.interface"; 4 + MAIL_SERVICE_TOKEN, MailService, } from "../mail.service.interface"; 7 5 import { ConsoleMailService } from "../providers/console-mail.service"; 8 6 import { ResendMailService } from "./resend-mail.service"; 9 7
+1 -1
packages/mail/src/template/handlebars-template.service.ts
··· 1 1 import { existsSync, readFileSync } from "node:fs"; 2 2 import { join } from "node:path"; 3 - import { Injectable } from "@nestjs/common"; 4 3 import { createHandlebars } from "@cv/handlebars"; 4 + import { Injectable } from "@nestjs/common"; 5 5 import type Handlebars from "handlebars"; 6 6 import { TemplateRegistryService } from "./template-registry.service"; 7 7
+1 -1
packages/routing/src/ViewTransitionLink.tsx
··· 1 - import { type MouseEvent, type ReactNode } from "react"; 1 + import type { MouseEvent, ReactNode } from "react"; 2 2 import { Link, type LinkProps, useNavigate } from "react-router-dom"; 3 3 4 4 interface ViewTransitionLinkProps extends Omit<LinkProps, "onClick"> {
+8 -8
packages/tsconfig/package.json
··· 1 1 { 2 - "name": "@cv/tsconfig", 3 - "version": "0.0.0", 4 - "private": true, 5 - "files": [ 6 - "tsconfig.base.json", 7 - "tsconfig.node.json", 8 - "tsconfig.library.json" 9 - ] 2 + "name": "@cv/tsconfig", 3 + "version": "0.0.0", 4 + "private": true, 5 + "files": [ 6 + "tsconfig.base.json", 7 + "tsconfig.node.json", 8 + "tsconfig.library.json" 9 + ] 10 10 }
+29 -29
packages/tsconfig/tsconfig.base.json
··· 1 1 { 2 - "compilerOptions": { 3 - "target": "ES2022", 4 - "lib": ["ES2022", "DOM"], 5 - "module": "ESNext", 6 - "moduleResolution": "Bundler", 7 - "strict": true, 8 - "noFallthroughCasesInSwitch": true, 9 - "noImplicitOverride": true, 10 - "noPropertyAccessFromIndexSignature": true, 11 - "noUncheckedIndexedAccess": true, 12 - "noUnusedLocals": false, 13 - "noUnusedParameters": false, 14 - "skipLibCheck": true, 15 - "esModuleInterop": true, 16 - "forceConsistentCasingInFileNames": true, 17 - "resolveJsonModule": true, 18 - "isolatedModules": true, 19 - "exactOptionalPropertyTypes": true, 20 - "noImplicitReturns": true, 21 - "noImplicitThis": true, 22 - "strictNullChecks": true, 23 - "strictFunctionTypes": true, 24 - "strictBindCallApply": true, 25 - "strictPropertyInitialization": true, 26 - "noImplicitAny": true, 27 - "experimentalDecorators": true, 28 - "emitDecoratorMetadata": true 29 - }, 30 - "exclude": ["dist", "node_modules"] 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "lib": ["ES2022", "DOM"], 5 + "module": "ESNext", 6 + "moduleResolution": "Bundler", 7 + "strict": true, 8 + "noFallthroughCasesInSwitch": true, 9 + "noImplicitOverride": true, 10 + "noPropertyAccessFromIndexSignature": true, 11 + "noUncheckedIndexedAccess": true, 12 + "noUnusedLocals": false, 13 + "noUnusedParameters": false, 14 + "skipLibCheck": true, 15 + "esModuleInterop": true, 16 + "forceConsistentCasingInFileNames": true, 17 + "resolveJsonModule": true, 18 + "isolatedModules": true, 19 + "exactOptionalPropertyTypes": true, 20 + "noImplicitReturns": true, 21 + "noImplicitThis": true, 22 + "strictNullChecks": true, 23 + "strictFunctionTypes": true, 24 + "strictBindCallApply": true, 25 + "strictPropertyInitialization": true, 26 + "noImplicitAny": true, 27 + "experimentalDecorators": true, 28 + "emitDecoratorMetadata": true 29 + }, 30 + "exclude": ["dist", "node_modules"] 31 31 }
+7 -7
packages/tsconfig/tsconfig.library.json
··· 1 1 { 2 - "extends": "./tsconfig.base.json", 3 - "compilerOptions": { 4 - "declaration": true, 5 - "emitDeclarationOnly": false, 6 - "outDir": "dist" 7 - }, 8 - "include": ["src"] 2 + "extends": "./tsconfig.base.json", 3 + "compilerOptions": { 4 + "declaration": true, 5 + "emitDeclarationOnly": false, 6 + "outDir": "dist" 7 + }, 8 + "include": ["src"] 9 9 }
+7 -7
packages/tsconfig/tsconfig.node.json
··· 1 1 { 2 - "extends": "./tsconfig.base.json", 3 - "compilerOptions": { 4 - "module": "CommonJS", 5 - "moduleResolution": "Node", 6 - "outDir": "dist", 7 - "sourceMap": true 8 - } 2 + "extends": "./tsconfig.base.json", 3 + "compilerOptions": { 4 + "module": "CommonJS", 5 + "moduleResolution": "Node", 6 + "outDir": "dist", 7 + "sourceMap": true 8 + } 9 9 }
-1
packages/utils/__tests__/range.test.ts
··· 24 24 assert.throws(() => range(1.5), /non-negative integer/); 25 25 }); 26 26 }); 27 -
+6 -4
packages/utils/index.ts
··· 84 84 */ 85 85 export const compact = <T extends Record<string, unknown>>( 86 86 obj: T, 87 - ): { [K in keyof T as T[K] extends null | undefined ? never : K]: NonNullable<T[K]> } => 88 - Object.fromEntries( 89 - Object.entries(obj).filter(([, v]) => v != null), 90 - ) as never; 87 + ): { 88 + [K in keyof T as T[K] extends null | undefined ? never : K]: NonNullable< 89 + T[K] 90 + >; 91 + } => 92 + Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)) as never; 91 93 92 94 /** 93 95 * An object with a stable string identifier — the contract a `NamedRegistry`
+11 -11
packages/utils/package.json
··· 1 1 { 2 - "name": "@cv/utils", 3 - "version": "0.0.0", 4 - "private": true, 5 - "main": "index.ts", 6 - "types": "index.ts", 7 - "scripts": { 8 - "typecheck": "tsc -b" 9 - }, 10 - "devDependencies": { 11 - "@cv/tsconfig": "workspace:*" 12 - } 2 + "name": "@cv/utils", 3 + "version": "0.0.0", 4 + "private": true, 5 + "main": "index.ts", 6 + "types": "index.ts", 7 + "scripts": { 8 + "typecheck": "tsc -b" 9 + }, 10 + "devDependencies": { 11 + "@cv/tsconfig": "workspace:*" 12 + } 13 13 }
+11 -11
packages/utils/tsconfig.json
··· 1 1 { 2 - "extends": "@cv/tsconfig/tsconfig.base.json", 3 - "compilerOptions": { 4 - "outDir": "./dist", 5 - "rootDir": ".", 6 - "baseUrl": ".", 7 - "composite": true, 8 - "declaration": true, 9 - "emitDeclarationOnly": true 10 - }, 11 - "include": ["./**/*"], 12 - "exclude": ["dist", "node_modules", "__tests__"] 2 + "extends": "@cv/tsconfig/tsconfig.base.json", 3 + "compilerOptions": { 4 + "outDir": "./dist", 5 + "rootDir": ".", 6 + "baseUrl": ".", 7 + "composite": true, 8 + "declaration": true, 9 + "emitDeclarationOnly": true 10 + }, 11 + "include": ["./**/*"], 12 + "exclude": ["dist", "node_modules", "__tests__"] 13 13 }