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.

fix(CVG-44): delete remaining dead seed services and app/user/database/seed dirs in @cv/core

+2 -1170
-10
packages/core/src/modules/app/app.module.ts
··· 1 - import { Module } from "@nestjs/common"; 2 - import { AppService } from "./app.service"; 3 - import { HealthController } from "./health.controller"; 4 - 5 - @Module({ 6 - controllers: [HealthController], 7 - providers: [AppService], 8 - exports: [AppService], 9 - }) 10 - export class AppModule {}
-23
packages/core/src/modules/app/app.service.ts
··· 1 - import { Injectable } from "@nestjs/common"; 2 - 3 - export interface HealthResponse { 4 - status: string; 5 - timestamp: string; 6 - timezone: string; 7 - uptime: number; 8 - } 9 - 10 - @Injectable() 11 - export class AppService { 12 - private readonly startTime = Date.now(); 13 - 14 - getHealth(): HealthResponse { 15 - const now = new Date(); 16 - return { 17 - status: "ok", 18 - timestamp: now.toISOString(), 19 - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, 20 - uptime: Date.now() - this.startTime, 21 - }; 22 - } 23 - }
-12
packages/core/src/modules/app/health.controller.ts
··· 1 - import { Controller, Get } from "@nestjs/common"; 2 - import { AppService } from "./app.service"; 3 - 4 - @Controller("health") 5 - export class HealthController { 6 - constructor(private readonly appService: AppService) {} 7 - 8 - @Get() 9 - check() { 10 - return this.appService.getHealth(); 11 - } 12 - }
-2
packages/core/src/modules/cv-template/cv-template.module.ts
··· 11 11 import { CVTemplatePolicy } from "./cv-template.policy"; 12 12 import { CVTemplateService } from "./cv-template.service"; 13 13 import { PdfDownloadController } from "./pdf-download.controller"; 14 - import { CVTemplateSeedService } from "./seed/cv-template.seed"; 15 14 16 15 @Module({ 17 16 imports: [ ··· 27 26 CVService, 28 27 CVPolicy, 29 28 CVTemplatePolicy, 30 - CVTemplateSeedService, 31 29 CVDataAssemblerService, 32 30 CVRendererService, 33 31 ],
-89
packages/core/src/modules/cv-template/seed/cv-template.seed.ts
··· 1 - import { readFileSync } from "node:fs"; 2 - import { join } from "node:path"; 3 - import { PrismaService } from "../../database"; 4 - import { Injectable, Logger } from "@nestjs/common"; 5 - import { Seeder } from "../../database/seed/seed.service"; 6 - import { Seeder as SeederDecorator } from "../../database/seed/seeder.decorator"; 7 - 8 - const TEMPLATES_DIR = join(__dirname, "templates"); 9 - 10 - const readTemplate = (name: string) => ({ 11 - body: readFileSync(join(TEMPLATES_DIR, `${name}.hbs`), "utf-8"), 12 - css: readFileSync(join(TEMPLATES_DIR, `${name}.css`), "utf-8"), 13 - }); 14 - 15 - interface TemplateData { 16 - name: string; 17 - description: string; 18 - body: string; 19 - css: string; 20 - engine: string; 21 - } 22 - 23 - const templates: TemplateData[] = [ 24 - { 25 - name: "Modern Professional", 26 - description: "A clean, modern template perfect for tech professionals", 27 - ...readTemplate("modern-professional"), 28 - engine: "handlebars", 29 - }, 30 - { 31 - name: "Classic Executive", 32 - description: "A traditional template suitable for executive positions", 33 - ...readTemplate("classic-executive"), 34 - engine: "handlebars", 35 - }, 36 - { 37 - name: "Creative Portfolio", 38 - description: "A creative template for designers and artists", 39 - ...readTemplate("creative-portfolio"), 40 - engine: "handlebars", 41 - }, 42 - { 43 - name: "Minimal Clean", 44 - description: 45 - "Ultra-clean, whitespace-heavy Swiss design for tech and design roles", 46 - ...readTemplate("minimal-clean"), 47 - engine: "handlebars", 48 - }, 49 - { 50 - name: "Technical Resume", 51 - description: "Dense two-column layout with skills sidebar for developers", 52 - ...readTemplate("technical-resume"), 53 - engine: "handlebars", 54 - }, 55 - ]; 56 - 57 - @Injectable() 58 - @SeederDecorator("CV Templates") 59 - export class CVTemplateSeedService implements Seeder { 60 - private readonly logger = new Logger(CVTemplateSeedService.name); 61 - 62 - constructor(readonly _prisma: PrismaService) {} 63 - 64 - async seed(prisma: PrismaService): Promise<void> { 65 - this.logger.log("Seeding CV templates..."); 66 - 67 - await Promise.all( 68 - templates.map(async (template) => { 69 - const existing = await prisma["cVTemplate"].findFirst({ 70 - where: { name: template.name }, 71 - }); 72 - 73 - if (existing) { 74 - await prisma["cVTemplate"].update({ 75 - where: { id: existing.id }, 76 - data: { 77 - description: template.description, 78 - body: template.body, 79 - css: template.css, 80 - engine: template.engine, 81 - }, 82 - }); 83 - } else { 84 - await prisma["cVTemplate"].create({ data: template }); 85 - } 86 - }), 87 - ); 88 - } 89 - }
-24
packages/core/src/modules/database/seed/seed.module.ts
··· 1 - import { DatabaseModule } from ".."; 2 - import { Module } from "@nestjs/common"; 3 - import { ConfigModule } from "@nestjs/config"; 4 - import { DiscoveryModule } from "@nestjs/core"; 5 - import { AuthorizationModule } from "../../auth/authorization/authorization.module"; 6 - import { TokenModule } from "../../auth/token/token.module"; 7 - import { UserModule } from "../../auth/user/user.module"; 8 - import { CVTemplateSeedService } from "../../cv-template/seed/cv-template.seed"; 9 - import { UserSeedService } from "../../user/seed/user.seed"; 10 - import { SeedService } from "./seed.service"; 11 - 12 - @Module({ 13 - imports: [ 14 - ConfigModule.forRoot(), 15 - DatabaseModule, 16 - DiscoveryModule, 17 - UserModule, 18 - TokenModule, 19 - AuthorizationModule, 20 - ], 21 - providers: [SeedService, UserSeedService, CVTemplateSeedService], 22 - exports: [SeedService], 23 - }) 24 - export class SeedModule {}
-121
packages/core/src/modules/database/seed/seed.service.ts
··· 1 - import { PrismaService } from ".."; 2 - import { Injectable, Logger, OnModuleInit } from "@nestjs/common"; 3 - import { DiscoveryService } from "@nestjs/core"; 4 - import type { SeederConfig } from "./seeder.decorator"; 5 - import { SEEDER_METADATA_KEY } from "./seeder.decorator"; 6 - import { topologicalSort } from "./topological-sort"; 7 - 8 - export interface Seeder { 9 - seed(prisma: PrismaService): Promise<void>; 10 - } 11 - 12 - const parseMetadata = ( 13 - metadata: unknown, 14 - fallbackName: string, 15 - ): { name: string; dependsOn: string[] } => { 16 - if (typeof metadata === "string") return { name: metadata, dependsOn: [] }; 17 - if (typeof metadata === "object" && metadata !== null) { 18 - const config = metadata as SeederConfig; 19 - return { 20 - name: config.name ?? fallbackName, 21 - dependsOn: config.dependsOn ?? [], 22 - }; 23 - } 24 - return { name: fallbackName, dependsOn: [] }; 25 - }; 26 - 27 - @Injectable() 28 - export class SeedService implements OnModuleInit { 29 - private readonly logger = new Logger(SeedService.name); 30 - private readonly seeders: Array<{ 31 - name: string; 32 - dependsOn: string[]; 33 - seeder: Seeder; 34 - }> = []; 35 - 36 - constructor( 37 - private readonly prisma: PrismaService, 38 - private readonly discoveryService: DiscoveryService, 39 - ) {} 40 - 41 - async onModuleInit(): Promise<void> { 42 - const providers = this.discoveryService.getProviders(); 43 - 44 - for (const wrapper of providers) { 45 - if (!wrapper.isDependencyTreeStatic() || !wrapper.instance) continue; 46 - 47 - const instance = wrapper.instance; 48 - const seederMetadata = Reflect.getMetadata( 49 - SEEDER_METADATA_KEY, 50 - instance.constructor, 51 - ); 52 - 53 - if (!seederMetadata || typeof instance.seed !== "function") continue; 54 - 55 - const { name, dependsOn } = parseMetadata( 56 - seederMetadata, 57 - instance.constructor.name, 58 - ); 59 - this.seeders.push({ name, dependsOn, seeder: instance }); 60 - this.logger.log(`Registered seeder: ${name}`); 61 - } 62 - } 63 - 64 - async seed(): Promise<void> { 65 - this.logger.log("Starting database seeding..."); 66 - 67 - const result = topologicalSort( 68 - this.seeders.map((s) => ({ 69 - name: s.name, 70 - dependsOn: s.dependsOn, 71 - value: s.seeder, 72 - })), 73 - ); 74 - 75 - if ("cycle" in result) { 76 - throw new Error( 77 - `Circular seeder dependencies detected: ${result.cycle.join(" -> ")}`, 78 - ); 79 - } 80 - 81 - this.logger.log( 82 - `Execution order: ${result.sorted.map((s) => s.name).join(" -> ")}`, 83 - ); 84 - 85 - const failures: Array<{ name: string; error: unknown }> = []; 86 - 87 - for (const { name, value: seeder } of result.sorted) { 88 - try { 89 - this.logger.log(`Seeding: ${name}...`); 90 - await seeder.seed(this.prisma); 91 - } catch (error) { 92 - const message = error instanceof Error ? error.message : String(error); 93 - const code = 94 - error instanceof Error && "code" in error 95 - ? ` [Prisma ${(error as { code: string }).code}]` 96 - : ""; 97 - this.logger.error(`Seeder "${name}" failed${code}: ${message}`); 98 - failures.push({ name, error }); 99 - } 100 - } 101 - 102 - if (failures.length > 0) { 103 - const tablesMissing = failures.some( 104 - (f) => 105 - f.error instanceof Error && 106 - "code" in f.error && 107 - (f.error as { code: string }).code === "P2021", 108 - ); 109 - if (tablesMissing) { 110 - this.logger.error( 111 - "Hint: a 'table does not exist' error means migrations have not been applied. Run `pnpm prisma:deploy` (or `pnpm db:setup` to deploy + seed) first.", 112 - ); 113 - } 114 - throw new Error( 115 - `Seeders failed: ${failures.map((f) => f.name).join(", ")}`, 116 - ); 117 - } 118 - 119 - this.logger.log("Database seeding completed!"); 120 - } 121 - }
-11
packages/core/src/modules/database/seed/seeder.decorator.ts
··· 1 - import { SetMetadata } from "@nestjs/common"; 2 - 3 - export const SEEDER_METADATA_KEY = "seeder"; 4 - 5 - export interface SeederConfig { 6 - name?: string; 7 - dependsOn?: string[]; 8 - } 9 - 10 - export const Seeder = (config?: string | SeederConfig) => 11 - SetMetadata(SEEDER_METADATA_KEY, config ?? true);
-91
packages/core/src/modules/database/seed/topological-sort.spec.ts
··· 1 - import { describe, expect, it } from "vitest"; 2 - import { topologicalSort } from "./topological-sort"; 3 - 4 - describe("topologicalSort", () => { 5 - it("returns empty array for empty input", () => { 6 - const result = topologicalSort([]); 7 - expect(result).toEqual({ sorted: [] }); 8 - }); 9 - 10 - it("returns nodes with no dependencies in stable order", () => { 11 - const result = topologicalSort([ 12 - { name: "A", dependsOn: [], value: 1 }, 13 - { name: "B", dependsOn: [], value: 2 }, 14 - { name: "C", dependsOn: [], value: 3 }, 15 - ]); 16 - 17 - expect(result).toEqual({ 18 - sorted: [ 19 - { name: "A", value: 1 }, 20 - { name: "B", value: 2 }, 21 - { name: "C", value: 3 }, 22 - ], 23 - }); 24 - }); 25 - 26 - it("sorts a linear dependency chain", () => { 27 - const result = topologicalSort([ 28 - { name: "C", dependsOn: ["B"], value: 3 }, 29 - { name: "A", dependsOn: [], value: 1 }, 30 - { name: "B", dependsOn: ["A"], value: 2 }, 31 - ]); 32 - 33 - expect("sorted" in result).toBe(true); 34 - if (!("sorted" in result)) return; 35 - 36 - const names = result.sorted.map((n) => n.name); 37 - expect(names.indexOf("A")).toBeLessThan(names.indexOf("B")); 38 - expect(names.indexOf("B")).toBeLessThan(names.indexOf("C")); 39 - }); 40 - 41 - it("sorts branching dependencies", () => { 42 - const result = topologicalSort([ 43 - { name: "D", dependsOn: ["B", "C"], value: 4 }, 44 - { name: "B", dependsOn: ["A"], value: 2 }, 45 - { name: "C", dependsOn: ["A"], value: 3 }, 46 - { name: "A", dependsOn: [], value: 1 }, 47 - ]); 48 - 49 - expect("sorted" in result).toBe(true); 50 - if (!("sorted" in result)) return; 51 - 52 - const names = result.sorted.map((n) => n.name); 53 - expect(names.indexOf("A")).toBeLessThan(names.indexOf("B")); 54 - expect(names.indexOf("A")).toBeLessThan(names.indexOf("C")); 55 - expect(names.indexOf("B")).toBeLessThan(names.indexOf("D")); 56 - expect(names.indexOf("C")).toBeLessThan(names.indexOf("D")); 57 - }); 58 - 59 - it("detects circular dependencies", () => { 60 - const result = topologicalSort([ 61 - { name: "A", dependsOn: ["B"], value: 1 }, 62 - { name: "B", dependsOn: ["A"], value: 2 }, 63 - ]); 64 - 65 - expect("cycle" in result).toBe(true); 66 - if (!("cycle" in result)) return; 67 - 68 - expect(result.cycle).toContain("A"); 69 - expect(result.cycle).toContain("B"); 70 - }); 71 - 72 - it("throws on unknown dependency names", () => { 73 - expect(() => 74 - topologicalSort([ 75 - { name: "A", dependsOn: ["NonExistent"], value: 1 }, 76 - ]), 77 - ).toThrow("Unknown seeder dependencies: NonExistent"); 78 - }); 79 - 80 - it("preserves values through sorting", () => { 81 - const obj = { data: "test" }; 82 - const result = topologicalSort([ 83 - { name: "A", dependsOn: [], value: obj }, 84 - ]); 85 - 86 - expect("sorted" in result).toBe(true); 87 - if (!("sorted" in result)) return; 88 - 89 - expect(result.sorted[0].value).toBe(obj); 90 - }); 91 - });
-61
packages/core/src/modules/database/seed/topological-sort.ts
··· 1 - /** 2 - * Topologically sorts nodes using Kahn's algorithm. 3 - * Returns sorted order or the cycle that prevents resolution. 4 - */ 5 - export const topologicalSort = <T>( 6 - nodes: Array<{ name: string; dependsOn: string[]; value: T }>, 7 - ): { sorted: Array<{ name: string; value: T }> } | { cycle: string[] } => { 8 - const nameSet = new Set(nodes.map((n) => n.name)); 9 - 10 - const unknownDeps = nodes.flatMap((n) => 11 - n.dependsOn.filter((dep) => !nameSet.has(dep)), 12 - ); 13 - if (unknownDeps.length > 0) { 14 - throw new Error( 15 - `Unknown seeder dependencies: ${[...new Set(unknownDeps)].join(", ")}`, 16 - ); 17 - } 18 - 19 - const inDegree = new Map<string, number>(); 20 - const adjacency = new Map<string, string[]>(); 21 - const valueMap = new Map<string, T>(); 22 - 23 - nodes.forEach((n) => { 24 - inDegree.set(n.name, 0); 25 - adjacency.set(n.name, []); 26 - valueMap.set(n.name, n.value); 27 - }); 28 - 29 - nodes.forEach((n) => { 30 - n.dependsOn.forEach((dep) => { 31 - adjacency.get(dep)!.push(n.name); 32 - inDegree.set(n.name, inDegree.get(n.name)! + 1); 33 - }); 34 - }); 35 - 36 - const queue = nodes 37 - .filter((n) => inDegree.get(n.name) === 0) 38 - .map((n) => n.name); 39 - 40 - const sorted: Array<{ name: string; value: T }> = []; 41 - 42 - while (queue.length > 0) { 43 - const current = queue.shift()!; 44 - sorted.push({ name: current, value: valueMap.get(current)! }); 45 - 46 - for (const neighbor of adjacency.get(current)!) { 47 - const newDegree = inDegree.get(neighbor)! - 1; 48 - inDegree.set(neighbor, newDegree); 49 - if (newDegree === 0) queue.push(neighbor); 50 - } 51 - } 52 - 53 - if (sorted.length !== nodes.length) { 54 - const cycle = nodes 55 - .filter((n) => inDegree.get(n.name)! > 0) 56 - .map((n) => n.name); 57 - return { cycle }; 58 - } 59 - 60 - return { sorted }; 61 - };
-94
packages/core/src/modules/job-experience/seed/job-experience.seed.ts
··· 1 - import { PrismaService } from "../../database"; 2 - import { faker } from "@faker-js/faker"; 3 - import { Injectable, Logger } from "@nestjs/common"; 4 - import { Seeder } from "../../database/seed/seed.service"; 5 - import { Seeder as SeederDecorator } from "../../database/seed/seeder.decorator"; 6 - import { ReferenceDataSeedService } from "./reference-data.seed"; 7 - 8 - @Injectable() 9 - @SeederDecorator({ name: "Job Experiences", dependsOn: ["Users", "Reference Data"] }) 10 - export class JobExperienceSeedService implements Seeder { 11 - private readonly logger = new Logger(JobExperienceSeedService.name); 12 - 13 - constructor( 14 - readonly _prisma: PrismaService, 15 - private readonly refs: ReferenceDataSeedService, 16 - ) {} 17 - 18 - async seed(prisma: PrismaService): Promise<void> { 19 - this.logger.log("Seeding job experiences..."); 20 - 21 - // First ensure reference data exists 22 - const [skills, companies, roles, levels] = await Promise.all([ 23 - this.refs.ensureSkills(prisma), 24 - this.refs.ensureCompanies(prisma), 25 - this.refs.ensureRoles(prisma), 26 - this.refs.ensureLevels(prisma), 27 - ]); 28 - 29 - // Get test user by email from credentials 30 - const credentials = await prisma["credentials"].findUnique({ 31 - where: { email: "test@test.test" }, 32 - include: { user: true }, 33 - }); 34 - const testUser = credentials?.user ?? null; 35 - 36 - if (!testUser) { 37 - this.logger.warn("Test user not found, skipping job experience seeding"); 38 - return; 39 - } 40 - 41 - // Get or create profile for the test user 42 - const existingProfile = await prisma.profile.findFirst({ 43 - where: { userId: testUser.id }, 44 - }); 45 - const profile = existingProfile ?? await prisma.profile.create({ 46 - data: { userId: testUser.id, name: testUser.name }, 47 - }); 48 - 49 - // Clear existing job experiences for this profile only 50 - this.logger.log("Clearing existing job experiences for test user..."); 51 - await prisma.userJobExperience.deleteMany({ 52 - where: { profileId: profile.id }, 53 - }); 54 - 55 - // Create job experiences for the test user 56 - const numExperiences = faker.number.int({ min: 2, max: 5 }); 57 - 58 - await Promise.all( 59 - Array.from({ length: numExperiences }, async () => { 60 - const startDate = faker.date.past({ years: 8 }); 61 - const endDate = faker.datatype.boolean() 62 - ? faker.date.between({ from: startDate, to: new Date() }) 63 - : null; 64 - 65 - const company = faker.helpers.arrayElement(companies); 66 - const role = faker.helpers.arrayElement(roles); 67 - const level = faker.helpers.arrayElement(levels); 68 - 69 - // Select 3-8 random skills for this job experience 70 - const selectedSkills = faker.helpers.arrayElements(skills, { 71 - min: 3, 72 - max: 8, 73 - }); 74 - 75 - return prisma.userJobExperience.create({ 76 - data: { 77 - profileId: profile.id, 78 - companyId: company.id, 79 - roleId: role.id, 80 - levelId: level.id, 81 - startDate, 82 - endDate, 83 - description: faker.lorem.paragraph(), 84 - skills: { 85 - connect: selectedSkills.map((skill) => ({ id: skill.id })), 86 - }, 87 - }, 88 - }); 89 - }), 90 - ); 91 - 92 - this.logger.log(`Created ${numExperiences} job experiences for test user`); 93 - } 94 - }
-196
packages/core/src/modules/job-experience/seed/reference-data.seed.ts
··· 1 - import { PrismaService } from "../../database"; 2 - import { faker } from "@faker-js/faker"; 3 - import { Injectable, Logger } from "@nestjs/common"; 4 - import { Seeder } from "../../database/seed/seed.service"; 5 - import { Seeder as SeederDecorator } from "../../database/seed/seeder.decorator"; 6 - 7 - @Injectable() 8 - @SeederDecorator("Reference Data") 9 - export class ReferenceDataSeedService implements Seeder { 10 - private readonly logger = new Logger(ReferenceDataSeedService.name); 11 - 12 - constructor(private readonly prisma: PrismaService) {} 13 - 14 - async seed(prisma: PrismaService): Promise<void> { 15 - this.logger.log("Seeding reference data..."); 16 - await Promise.all([ 17 - this.ensureSkills(prisma), 18 - this.ensureCompanies(prisma), 19 - this.ensureRoles(prisma), 20 - this.ensureLevels(prisma), 21 - this.ensureJobTypes(prisma), 22 - this.ensureApplicationStatuses(prisma), 23 - this.ensureRateTypes(prisma), 24 - this.ensureContractTypes(prisma), 25 - ]); 26 - } 27 - 28 - async ensureSkills( 29 - prisma: PrismaService = this.prisma, 30 - ): Promise<Array<{ id: string }>> { 31 - const skills = ["React", "TypeScript", "Node.js", "GraphQL", "PostgreSQL"]; 32 - 33 - return Promise.all( 34 - skills.map(async (name) => { 35 - const existing = await prisma["skill"].findFirst({ 36 - where: { name }, 37 - }); 38 - if (existing) { 39 - return { id: existing.id }; 40 - } 41 - const s = await prisma["skill"].create({ 42 - data: { name, description: faker.lorem.sentence() }, 43 - }); 44 - return { id: s.id }; 45 - }), 46 - ); 47 - } 48 - 49 - async ensureCompanies( 50 - prisma: PrismaService = this.prisma, 51 - ): Promise<Array<{ id: string }>> { 52 - const names = ["Google", "Microsoft", "Amazon", "Netflix"]; 53 - 54 - return Promise.all( 55 - names.map(async (name) => { 56 - const existing = await prisma["company"].findFirst({ 57 - where: { name }, 58 - }); 59 - if (existing) { 60 - return { id: existing.id }; 61 - } 62 - const c = await prisma["company"].create({ 63 - data: { 64 - name, 65 - description: faker.company.catchPhrase(), 66 - website: faker.internet.url(), 67 - }, 68 - }); 69 - return { id: c.id }; 70 - }), 71 - ); 72 - } 73 - 74 - async ensureRoles( 75 - prisma: PrismaService = this.prisma, 76 - ): Promise<Array<{ id: string }>> { 77 - const names = [ 78 - "Software Engineer", 79 - "Senior Software Engineer", 80 - "Engineering Manager", 81 - ]; 82 - 83 - return Promise.all( 84 - names.map(async (name) => { 85 - const existing = await prisma["role"].findFirst({ where: { name } }); 86 - if (existing) { 87 - return { id: existing.id }; 88 - } 89 - const r = await prisma["role"].create({ 90 - data: { name, description: faker.lorem.sentence() }, 91 - }); 92 - return { id: r.id }; 93 - }), 94 - ); 95 - } 96 - 97 - async ensureLevels( 98 - prisma: PrismaService = this.prisma, 99 - ): Promise<Array<{ id: string }>> { 100 - const names = ["Junior", "Mid-level", "Senior"]; 101 - 102 - return Promise.all( 103 - names.map(async (name) => { 104 - const existing = await prisma["level"].findFirst({ 105 - where: { name }, 106 - }); 107 - if (existing) { 108 - return { id: existing.id }; 109 - } 110 - const l = await prisma["level"].create({ 111 - data: { name, description: faker.lorem.sentence() }, 112 - }); 113 - return { id: l.id }; 114 - }), 115 - ); 116 - } 117 - 118 - async ensureJobTypes( 119 - prisma: PrismaService = this.prisma, 120 - ): Promise<Array<{ id: string }>> { 121 - const names = [ 122 - "Full-time", 123 - "Part-time", 124 - "Freelance", 125 - "Contract", 126 - "Internship", 127 - "Temporary", 128 - ]; 129 - 130 - return Promise.all( 131 - names.map(async (name) => { 132 - const existing = await prisma["jobType"].findFirst({ 133 - where: { name }, 134 - }); 135 - if (existing) { 136 - return { id: existing.id }; 137 - } 138 - const jt = await prisma["jobType"].create({ 139 - data: { name, description: faker.lorem.sentence() }, 140 - }); 141 - return { id: jt.id }; 142 - }), 143 - ); 144 - } 145 - 146 - async ensureRateTypes( 147 - prisma: PrismaService = this.prisma, 148 - ): Promise<Array<{ id: string }>> { 149 - const names = ["HOUR", "AGREEMENT", "FIXED", "DAY"]; 150 - 151 - return Promise.all( 152 - names.map(async (name) => { 153 - const existing = await prisma.rateType.findFirst({ where: { name } }); 154 - if (existing) return { id: existing.id }; 155 - const rt = await prisma.rateType.create({ data: { name } }); 156 - return { id: rt.id }; 157 - }), 158 - ); 159 - } 160 - 161 - async ensureContractTypes( 162 - prisma: PrismaService = this.prisma, 163 - ): Promise<Array<{ id: string }>> { 164 - const names = ["FREELANCE", "FIXED_TERM", "PERMANENT", "INTERNSHIP"]; 165 - 166 - return Promise.all( 167 - names.map(async (name) => { 168 - const existing = await prisma.contractType.findFirst({ where: { name } }); 169 - if (existing) return { id: existing.id }; 170 - const ct = await prisma.contractType.create({ data: { name } }); 171 - return { id: ct.id }; 172 - }), 173 - ); 174 - } 175 - 176 - async ensureApplicationStatuses( 177 - prisma: PrismaService = this.prisma, 178 - ): Promise<Array<{ id: string }>> { 179 - const names = ["Pending", "Accepted", "Rejected", "Withdrawn"]; 180 - 181 - return Promise.all( 182 - names.map(async (name) => { 183 - const existing = await prisma["applicationStatus"].findFirst({ 184 - where: { name }, 185 - }); 186 - if (existing) { 187 - return { id: existing.id }; 188 - } 189 - const status = await prisma["applicationStatus"].create({ 190 - data: { name, description: faker.lorem.sentence() }, 191 - }); 192 - return { id: status.id }; 193 - }), 194 - ); 195 - } 196 - }
+1 -10
packages/core/src/modules/job-experience/skill/skill.module.ts
··· 4 4 import { Module } from "@nestjs/common"; 5 5 import { AuthenticationModule } from "../../authentication/authentication.module"; 6 6 import { SkillService } from "./skill.service"; 7 - import { JobExperienceSeedService } from "../seed/job-experience.seed"; 8 - import { ReferenceDataSeedService } from "../seed/reference-data.seed"; 9 7 import { SkillFactory } from "./skill.factory"; 10 8 import { SkillMapper } from "./skill.mapper"; 11 9 import { SkillPolicy } from "./skill.policy"; ··· 17 15 AuthenticationModule, 18 16 AuthorizationModule, 19 17 ], 20 - providers: [ 21 - SkillService, 22 - SkillMapper, 23 - SkillFactory, 24 - SkillPolicy, 25 - ReferenceDataSeedService, 26 - JobExperienceSeedService, 27 - ], 18 + providers: [SkillService, SkillMapper, SkillFactory, SkillPolicy], 28 19 exports: [SkillService, SkillMapper], 29 20 }) 30 21 export class SkillModule {}
-4
packages/core/src/modules/organization/organization.module.ts
··· 10 10 import { OrganizationRoleMapper } from "./organization-role.mapper"; 11 11 import { OrganizationRolePolicy } from "./organization-role.policy"; 12 12 import { OrganizationRoleService } from "./organization-role.service"; 13 - import { MembershipSeedService } from "./seed/membership.seed"; 14 - import { OrganizationSeedService } from "./seed/organization.seed"; 15 13 16 14 @Module({ 17 15 imports: [ ··· 28 26 OrganizationRolePolicy, 29 27 OrganizationMapper, 30 28 OrganizationRoleMapper, 31 - OrganizationSeedService, 32 - MembershipSeedService, 33 29 ], 34 30 exports: [ 35 31 OrganizationService,
-134
packages/core/src/modules/organization/seed/membership.seed.ts
··· 1 - import { PrismaService } from "../../database"; 2 - import { faker } from "@faker-js/faker"; 3 - import { Injectable, Logger } from "@nestjs/common"; 4 - import { Seeder } from "../../database/seed/seed.service"; 5 - import { Seeder as SeederDecorator } from "../../database/seed/seeder.decorator"; 6 - 7 - @Injectable() 8 - @SeederDecorator({ name: "Memberships", dependsOn: ["Users", "Organizations"] }) 9 - export class MembershipSeedService implements Seeder { 10 - private readonly logger = new Logger(MembershipSeedService.name); 11 - 12 - constructor(readonly _prisma: PrismaService) {} 13 - 14 - private async ensureMemberRole( 15 - prisma: PrismaService, 16 - ): Promise<{ id: string }> { 17 - const existing = await prisma["organizationRole"].findFirst({ 18 - where: { name: "Member" }, 19 - }); 20 - 21 - if (existing) return { id: existing.id }; 22 - 23 - return prisma["organizationRole"].create({ 24 - data: { name: "Member", description: "Default member role" }, 25 - }); 26 - } 27 - 28 - private async createMembershipIfMissing( 29 - prisma: PrismaService, 30 - userId: string, 31 - organizationId: string, 32 - roleId: string, 33 - ): Promise<void> { 34 - const existing = await prisma["membership"].findFirst({ 35 - where: { userId, organizationId }, 36 - }); 37 - 38 - if (!existing) { 39 - await prisma["membership"].create({ 40 - data: { userId, organizationId, organizationRoleId: roleId }, 41 - }); 42 - } 43 - } 44 - 45 - async seed(prisma: PrismaService): Promise<void> { 46 - this.logger.log("Seeding memberships..."); 47 - 48 - const memberRole = await this.ensureMemberRole(prisma); 49 - 50 - const [allUsers, allOrganizations] = await Promise.all([ 51 - prisma["user"].findMany(), 52 - prisma["organization"].findMany(), 53 - ]); 54 - 55 - if (allUsers.length === 0 || allOrganizations.length === 0) { 56 - this.logger.warn( 57 - "No users or organizations found, skipping membership seeding", 58 - ); 59 - return; 60 - } 61 - 62 - await this.ensureTestUserMemberships( 63 - prisma, 64 - allOrganizations, 65 - memberRole.id, 66 - ); 67 - 68 - const allCredentials = await prisma["credentials"].findMany({ 69 - include: { user: true }, 70 - }); 71 - const testUserCredentials = allCredentials.find( 72 - (c: { email: string }) => c.email === "test@test.test", 73 - ); 74 - const testUserIds = testUserCredentials ? [testUserCredentials.userId] : []; 75 - 76 - await Promise.all( 77 - allUsers.map(async (user: { id: string }) => { 78 - if (testUserIds.includes(user.id)) return; 79 - 80 - const numOrganizations = faker.number.int({ min: 1, max: 2 }); 81 - const selectedOrganizations = faker.helpers.arrayElements( 82 - allOrganizations, 83 - { min: 1, max: numOrganizations }, 84 - ); 85 - 86 - await Promise.all( 87 - selectedOrganizations.map((org: { id: string }) => 88 - this.createMembershipIfMissing( 89 - prisma, 90 - user.id, 91 - org.id, 92 - memberRole.id, 93 - ), 94 - ), 95 - ); 96 - }), 97 - ); 98 - 99 - this.logger.log("Assigned users to organizations"); 100 - } 101 - 102 - private async ensureTestUserMemberships( 103 - prisma: PrismaService, 104 - allOrganizations: Array<{ id: string }>, 105 - roleId: string, 106 - ): Promise<void> { 107 - const testCredentials = await prisma["credentials"].findUnique({ 108 - where: { email: "test@test.test" }, 109 - include: { user: true }, 110 - }); 111 - 112 - if (!testCredentials) { 113 - this.logger.warn( 114 - "Test user not found, skipping test user membership assignment", 115 - ); 116 - return; 117 - } 118 - 119 - const testUserId = testCredentials.userId; 120 - const organizationsForTestUser = allOrganizations.slice(0, 5); 121 - 122 - await Promise.all( 123 - organizationsForTestUser.map(async (org) => { 124 - this.logger.log(`Adding test@test.test to organization ${org.id}`); 125 - await this.createMembershipIfMissing( 126 - prisma, 127 - testUserId, 128 - org.id, 129 - roleId, 130 - ); 131 - }), 132 - ); 133 - } 134 - }
-57
packages/core/src/modules/organization/seed/organization.seed.ts
··· 1 - import { PrismaService } from "../../database"; 2 - import { faker } from "@faker-js/faker"; 3 - import { Injectable, Logger } from "@nestjs/common"; 4 - import { Seeder } from "../../database/seed/seed.service"; 5 - import { Seeder as SeederDecorator } from "../../database/seed/seeder.decorator"; 6 - 7 - @Injectable() 8 - @SeederDecorator("Organizations") 9 - export class OrganizationSeedService implements Seeder { 10 - private readonly logger = new Logger(OrganizationSeedService.name); 11 - 12 - constructor(private readonly prisma: PrismaService) {} 13 - 14 - async seed(prisma: PrismaService): Promise<void> { 15 - this.logger.log("Seeding organizations..."); 16 - await this.ensureOrganizations(prisma); 17 - } 18 - 19 - async ensureOrganizations( 20 - prisma: PrismaService = this.prisma, 21 - ): Promise<Array<{ id: string }>> { 22 - const names = [ 23 - "TechCorp Solutions", 24 - "Innovation Labs", 25 - "Digital Dynamics", 26 - "Future Systems", 27 - "Alpha Technologies", 28 - "Beta Solutions", 29 - "Gamma Industries", 30 - "Delta Enterprises", 31 - "Epsilon Corp", 32 - "Zeta Systems", 33 - "Eta Technologies", 34 - "Theta Labs", 35 - "Iota Innovations", 36 - "Kappa Dynamics", 37 - "Lambda Solutions", 38 - ]; 39 - 40 - const created = await Promise.all( 41 - names.map(async (name) => { 42 - const existing = await prisma["organization"].findFirst({ 43 - where: { name }, 44 - }); 45 - if (existing) { 46 - return { id: existing.id }; 47 - } 48 - const org = await prisma["organization"].create({ 49 - data: { name, description: faker.company.catchPhrase() }, 50 - }); 51 - return { id: org.id }; 52 - }), 53 - ); 54 - 55 - return created; 56 - } 57 - }
-131
packages/core/src/modules/user/seed/user.seed.ts
··· 1 - import { CredentialsService, UserService } from "../../auth"; 2 - import { PrismaService } from "../../database"; 3 - import { Injectable, Logger } from "@nestjs/common"; 4 - import { Seeder } from "../../database/seed/seed.service"; 5 - import { Seeder as SeederDecorator } from "../../database/seed/seeder.decorator"; 6 - 7 - @Injectable() 8 - @SeederDecorator("Users") 9 - export class UserSeedService implements Seeder { 10 - private readonly logger = new Logger(UserSeedService.name); 11 - 12 - constructor( 13 - private readonly _prisma: PrismaService, 14 - private readonly userService: UserService, 15 - private readonly credentialsService: CredentialsService, 16 - ) {} 17 - 18 - async seed(prisma: PrismaService): Promise<void> { 19 - this.logger.log("Seeding users..."); 20 - await this.ensureTestUser(); 21 - await this.ensureAdditionalTestUsers(); 22 - } 23 - 24 - async ensureTestUser(): Promise<{ id: string; email: string; name: string }> { 25 - const testEmail = "test@test.test"; 26 - let credentials = await this.credentialsService.findByEmail(testEmail); 27 - 28 - if (!credentials) { 29 - this.logger.log("Creating test user..."); 30 - const user = await this.userService.create("Test User"); 31 - credentials = await this.credentialsService.create( 32 - user.id, 33 - testEmail, 34 - // bcrypt hash for "password" 35 - "$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", 36 - ); 37 - await this._prisma["credentials"].update({ 38 - where: { userId: user.id }, 39 - data: { emailVerifiedAt: new Date() }, 40 - }); 41 - } 42 - 43 - const user = await this.userService.findByIdOrFail(credentials.userId); 44 - 45 - await this._prisma["user"].update({ 46 - where: { id: user.id }, 47 - data: { role: "ADMIN" }, 48 - }); 49 - 50 - await this.ensureDefaultProfile(user.id, "Test User"); 51 - 52 - return { 53 - id: user.id, 54 - email: credentials.email, 55 - name: user.name, 56 - }; 57 - } 58 - 59 - async ensureAdditionalTestUsers(): Promise< 60 - Array<{ id: string; email: string; name: string }> 61 - > { 62 - const testUsers = [ 63 - { email: "alice@example.com", name: "Alice Johnson" }, 64 - { email: "bob@example.com", name: "Bob Smith" }, 65 - { email: "carol@example.com", name: "Carol Davis" }, 66 - { email: "david@example.com", name: "David Wilson" }, 67 - { email: "eve@example.com", name: "Eve Brown" }, 68 - { email: "frank@example.com", name: "Frank Miller" }, 69 - { email: "grace@example.com", name: "Grace Taylor" }, 70 - { email: "henry@example.com", name: "Henry Anderson" }, 71 - { email: "ivy@example.com", name: "Ivy Thomas" }, 72 - { email: "jack@example.com", name: "Jack Jackson" }, 73 - { email: "karen@example.com", name: "Karen White" }, 74 - { email: "leo@example.com", name: "Leo Harris" }, 75 - { email: "mary@example.com", name: "Mary Martin" }, 76 - { email: "nick@example.com", name: "Nick Garcia" }, 77 - { email: "olivia@example.com", name: "Olivia Martinez" }, 78 - ]; 79 - 80 - const createdUsers = await Promise.all( 81 - testUsers.map(async (userData) => { 82 - let credentials = await this.credentialsService.findByEmail( 83 - userData.email, 84 - ); 85 - 86 - if (!credentials) { 87 - this.logger.log(`Creating test user: ${userData.name}`); 88 - const { id: userId } = await this.userService.create(userData.name); 89 - credentials = await this.credentialsService.create( 90 - userId, 91 - userData.email, 92 - // bcrypt hash for "password" 93 - "$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", 94 - ); 95 - await this._prisma["credentials"].update({ 96 - where: { userId }, 97 - data: { emailVerifiedAt: new Date() }, 98 - }); 99 - } 100 - 101 - const user = await this.userService.findByIdOrFail(credentials.userId); 102 - 103 - await this.ensureDefaultProfile(user.id, userData.name); 104 - 105 - return { 106 - id: user.id, 107 - email: credentials.email, 108 - name: user.name, 109 - }; 110 - }), 111 - ); 112 - 113 - return createdUsers; 114 - } 115 - 116 - private async ensureDefaultProfile( 117 - userId: string, 118 - name: string, 119 - ): Promise<void> { 120 - const existing = await this._prisma["profile"].findFirst({ 121 - where: { userId }, 122 - }); 123 - 124 - if (existing) return; 125 - 126 - this.logger.log(`Creating default profile for ${name}`); 127 - await this._prisma["profile"].create({ 128 - data: { userId, name: "Default" }, 129 - }); 130 - } 131 - }
-92
packages/core/src/modules/vacancies/seed/vacancy.seed.ts
··· 1 - import { PrismaService } from "../../database"; 2 - import { faker } from "@faker-js/faker"; 3 - import { Injectable, Logger } from "@nestjs/common"; 4 - import { Seeder } from "../../database/seed/seed.service"; 5 - import { Seeder as SeederDecorator } from "../../database/seed/seeder.decorator"; 6 - 7 - @Injectable() 8 - @SeederDecorator({ name: "Public Vacancies", dependsOn: ["Users", "Reference Data"] }) 9 - export class VacancySeedService implements Seeder { 10 - private readonly logger = new Logger(VacancySeedService.name); 11 - 12 - constructor(readonly _prisma: PrismaService) {} 13 - 14 - async seed(prisma: PrismaService): Promise<void> { 15 - this.logger.log("Seeding public vacancies..."); 16 - 17 - // Get reference data 18 - const [companies, roles, levels, jobTypes, users] = await Promise.all([ 19 - prisma["company"].findMany(), 20 - prisma["role"].findMany(), 21 - prisma["level"].findMany(), 22 - prisma["jobType"].findMany(), 23 - prisma["user"].findMany({ take: 10 }), 24 - ]); 25 - 26 - if ( 27 - companies.length === 0 || 28 - roles.length === 0 || 29 - levels.length === 0 || 30 - jobTypes.length === 0 || 31 - users.length === 0 32 - ) { 33 - this.logger.warn( 34 - "Missing reference data. Skipping vacancy seeding. Make sure reference data is seeded first.", 35 - ); 36 - return; 37 - } 38 - 39 - // Generate 50 public vacancies 40 - const vacanciesToCreate = Array.from({ length: 50 }, () => { 41 - const company = faker.helpers.arrayElement(companies) as { 42 - id: string; 43 - name: string; 44 - }; 45 - const role = faker.helpers.arrayElement(roles) as { 46 - id: string; 47 - name: string; 48 - }; 49 - const level = faker.helpers.arrayElement(levels) as { id: string }; 50 - const jobType = faker.helpers.arrayElement(jobTypes) as { id: string }; 51 - const owner = faker.helpers.arrayElement(users) as { id: string }; 52 - 53 - const minSalary = faker.datatype.boolean() 54 - ? faker.number.int({ min: 50000, max: 150000 }) 55 - : null; 56 - const maxSalary = minSalary 57 - ? faker.number.int({ min: minSalary, max: minSalary + 50000 }) 58 - : null; 59 - 60 - return { 61 - ownerId: owner.id, 62 - title: `${role.name} - ${company.name}`, 63 - companyId: company.id, 64 - roleId: role.id, 65 - levelId: level.id, 66 - jobTypeId: jobType.id, 67 - description: faker.lorem.paragraphs(3), 68 - requirements: faker.lorem.paragraphs(2), 69 - minSalary, 70 - maxSalary, 71 - applicationUrl: faker.internet.url(), 72 - deadline: faker.date.future(), 73 - isActive: true, 74 - isPublic: true, 75 - }; 76 - }); 77 - 78 - // Create vacancies in batches 79 - const batchSize = 10; 80 - for (let i = 0; i < vacanciesToCreate.length; i += batchSize) { 81 - const batch = vacanciesToCreate.slice(i, i + batchSize); 82 - await Promise.all( 83 - batch.map((data) => prisma["vacancy"].create({ data })), 84 - ); 85 - this.logger.log( 86 - `Created ${Math.min(i + batchSize, vacanciesToCreate.length)}/${vacanciesToCreate.length} vacancies...`, 87 - ); 88 - } 89 - 90 - this.logger.log(`✅ Created ${vacanciesToCreate.length} public vacancies`); 91 - } 92 - }
+1 -8
packages/core/src/modules/vacancies/vacancy.module.ts
··· 10 10 import { ContractTypeModule } from "./contract-type/contract-type.module"; 11 11 import { LocationModule } from "./location/location.module"; 12 12 import { RateTypeModule } from "./rate-type/rate-type.module"; 13 - import { VacancySeedService } from "./seed/vacancy.seed"; 14 13 import { VacancyFactory } from "./vacancy.factory"; 15 14 import { VacancyMapper } from "./vacancy.mapper"; 16 15 import { VacancyPolicy } from "./vacancy.policy"; ··· 30 29 AuthenticationModule, 31 30 AuthorizationModule, 32 31 ], 33 - providers: [ 34 - VacancyService, 35 - VacancyFactory, 36 - VacancyMapper, 37 - VacancyPolicy, 38 - VacancySeedService, 39 - ], 32 + providers: [VacancyService, VacancyFactory, VacancyMapper, VacancyPolicy], 40 33 exports: [VacancyService, VacancyMapper], 41 34 }) 42 35 export class VacancyModule {}