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(server): reorganize seed services and user module

+861
+45
apps/server/src/modules/cv-template/seed/cv-template.seed.ts
··· 1 + import { Injectable, Logger } from "@nestjs/common"; 2 + import { PrismaService } from "@/modules/database/prisma.service"; 3 + import { Seeder } from "@/modules/database/seed/seed.service"; 4 + import { Seeder as SeederDecorator } from "@/modules/database/seed/seeder.decorator"; 5 + 6 + @Injectable() 7 + @SeederDecorator("CV Templates") 8 + export class CVTemplateSeedService implements Seeder { 9 + private readonly logger = new Logger(CVTemplateSeedService.name); 10 + 11 + constructor(readonly _prisma: PrismaService) {} 12 + 13 + async seed(prisma: PrismaService): Promise<void> { 14 + this.logger.log("Seeding CV templates..."); 15 + 16 + const templates = [ 17 + { 18 + name: "Modern Professional", 19 + description: "A clean, modern template perfect for tech professionals", 20 + }, 21 + { 22 + name: "Classic Executive", 23 + description: "A traditional template suitable for executive positions", 24 + }, 25 + { 26 + name: "Creative Portfolio", 27 + description: "A creative template for designers and artists", 28 + }, 29 + ]; 30 + 31 + await Promise.all( 32 + templates.map(async (template) => { 33 + const existing = await prisma["cVTemplate"].findFirst({ 34 + where: { name: template.name }, 35 + }); 36 + 37 + if (!existing) { 38 + await prisma["cVTemplate"].create({ 39 + data: template, 40 + }); 41 + } 42 + }), 43 + ); 44 + } 45 + }
+84
apps/server/src/modules/job-experience/seed/job-experience.seed.ts
··· 1 + import { faker } from "@faker-js/faker"; 2 + import { Injectable, Logger } from "@nestjs/common"; 3 + import { PrismaService } from "@/modules/database/prisma.service"; 4 + import { Seeder } from "@/modules/database/seed/seed.service"; 5 + import { Seeder as SeederDecorator } from "@/modules/database/seed/seeder.decorator"; 6 + import { ReferenceDataSeedService } from "./reference-data.seed"; 7 + 8 + @Injectable() 9 + @SeederDecorator("Job Experiences") 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 30 + const testUser = await prisma["user"].findUnique({ 31 + where: { email: "test@test.test" }, 32 + }); 33 + 34 + if (!testUser) { 35 + this.logger.warn("Test user not found, skipping job experience seeding"); 36 + return; 37 + } 38 + 39 + // Clear existing job experiences for this user only 40 + this.logger.log("Clearing existing job experiences for test user..."); 41 + await prisma.userJobExperience.deleteMany({ 42 + where: { userId: testUser.id }, 43 + }); 44 + 45 + // Create job experiences for the test user 46 + const numExperiences = faker.number.int({ min: 2, max: 5 }); 47 + 48 + await Promise.all( 49 + Array.from({ length: numExperiences }, async () => { 50 + const startDate = faker.date.past({ years: 8 }); 51 + const endDate = faker.datatype.boolean() 52 + ? faker.date.between({ from: startDate, to: new Date() }) 53 + : null; 54 + 55 + const company = faker.helpers.arrayElement(companies); 56 + const role = faker.helpers.arrayElement(roles); 57 + const level = faker.helpers.arrayElement(levels); 58 + 59 + // Select 3-8 random skills for this job experience 60 + const selectedSkills = faker.helpers.arrayElements(skills, { 61 + min: 3, 62 + max: 8, 63 + }); 64 + 65 + return prisma.userJobExperience.create({ 66 + data: { 67 + userId: testUser.id, 68 + companyId: company.id, 69 + roleId: role.id, 70 + levelId: level.id, 71 + startDate, 72 + endDate, 73 + description: faker.lorem.paragraph(), 74 + skills: { 75 + connect: selectedSkills.map((skill) => ({ id: skill.id })), 76 + }, 77 + }, 78 + }); 79 + }), 80 + ); 81 + 82 + this.logger.log(`Created ${numExperiences} job experiences for test user`); 83 + } 84 + }
+164
apps/server/src/modules/job-experience/seed/reference-data.seed.ts
··· 1 + import { faker } from "@faker-js/faker"; 2 + import { Injectable, Logger } from "@nestjs/common"; 3 + import { PrismaService } from "@/modules/database/prisma.service"; 4 + import { Seeder } from "@/modules/database/seed/seed.service"; 5 + import { Seeder as SeederDecorator } from "@/modules/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 + ]); 24 + } 25 + 26 + async ensureSkills( 27 + prisma: PrismaService = this.prisma, 28 + ): Promise<Array<{ id: string }>> { 29 + const skills = ["React", "TypeScript", "Node.js", "GraphQL", "PostgreSQL"]; 30 + 31 + return Promise.all( 32 + skills.map(async (name) => { 33 + const existing = await prisma["skill"].findFirst({ 34 + where: { name }, 35 + }); 36 + if (existing) { 37 + return { id: existing.id }; 38 + } 39 + const s = await prisma["skill"].create({ 40 + data: { name, description: faker.lorem.sentence() }, 41 + }); 42 + return { id: s.id }; 43 + }), 44 + ); 45 + } 46 + 47 + async ensureCompanies( 48 + prisma: PrismaService = this.prisma, 49 + ): Promise<Array<{ id: string }>> { 50 + const names = ["Google", "Microsoft", "Amazon", "Netflix"]; 51 + 52 + return Promise.all( 53 + names.map(async (name) => { 54 + const existing = await prisma["company"].findFirst({ 55 + where: { name }, 56 + }); 57 + if (existing) { 58 + return { id: existing.id }; 59 + } 60 + const c = await prisma["company"].create({ 61 + data: { 62 + name, 63 + description: faker.company.catchPhrase(), 64 + website: faker.internet.url(), 65 + }, 66 + }); 67 + return { id: c.id }; 68 + }), 69 + ); 70 + } 71 + 72 + async ensureRoles( 73 + prisma: PrismaService = this.prisma, 74 + ): Promise<Array<{ id: string }>> { 75 + const names = [ 76 + "Software Engineer", 77 + "Senior Software Engineer", 78 + "Engineering Manager", 79 + ]; 80 + 81 + return Promise.all( 82 + names.map(async (name) => { 83 + const existing = await prisma["role"].findFirst({ where: { name } }); 84 + if (existing) { 85 + return { id: existing.id }; 86 + } 87 + const r = await prisma["role"].create({ 88 + data: { name, description: faker.lorem.sentence() }, 89 + }); 90 + return { id: r.id }; 91 + }), 92 + ); 93 + } 94 + 95 + async ensureLevels( 96 + prisma: PrismaService = this.prisma, 97 + ): Promise<Array<{ id: string }>> { 98 + const names = ["Junior", "Mid-level", "Senior"]; 99 + 100 + return Promise.all( 101 + names.map(async (name) => { 102 + const existing = await prisma["level"].findFirst({ 103 + where: { name }, 104 + }); 105 + if (existing) { 106 + return { id: existing.id }; 107 + } 108 + const l = await prisma["level"].create({ 109 + data: { name, description: faker.lorem.sentence() }, 110 + }); 111 + return { id: l.id }; 112 + }), 113 + ); 114 + } 115 + 116 + async ensureJobTypes( 117 + prisma: PrismaService = this.prisma, 118 + ): Promise<Array<{ id: string }>> { 119 + const names = [ 120 + "Full-time", 121 + "Part-time", 122 + "Freelance", 123 + "Contract", 124 + "Internship", 125 + "Temporary", 126 + ]; 127 + 128 + return Promise.all( 129 + names.map(async (name) => { 130 + const existing = await prisma["jobType"].findFirst({ 131 + where: { name }, 132 + }); 133 + if (existing) { 134 + return { id: existing.id }; 135 + } 136 + const jt = await prisma["jobType"].create({ 137 + data: { name, description: faker.lorem.sentence() }, 138 + }); 139 + return { id: jt.id }; 140 + }), 141 + ); 142 + } 143 + 144 + async ensureApplicationStatuses( 145 + prisma: PrismaService = this.prisma, 146 + ): Promise<Array<{ id: string }>> { 147 + const names = ["Pending", "Accepted", "Rejected", "Withdrawn"]; 148 + 149 + return Promise.all( 150 + names.map(async (name) => { 151 + const existing = await prisma["applicationStatus"].findFirst({ 152 + where: { name }, 153 + }); 154 + if (existing) { 155 + return { id: existing.id }; 156 + } 157 + const status = await prisma["applicationStatus"].create({ 158 + data: { name, description: faker.lorem.sentence() }, 159 + }); 160 + return { id: status.id }; 161 + }), 162 + ); 163 + } 164 + }
+123
apps/server/src/modules/organization/seed/membership.seed.ts
··· 1 + import { faker } from "@faker-js/faker"; 2 + import { Injectable, Logger } from "@nestjs/common"; 3 + import { PrismaService } from "@/modules/database/prisma.service"; 4 + import { Seeder } from "@/modules/database/seed/seed.service"; 5 + import { Seeder as SeederDecorator } from "@/modules/database/seed/seeder.decorator"; 6 + 7 + @Injectable() 8 + @SeederDecorator("Memberships") 9 + export class MembershipSeedService implements Seeder { 10 + private readonly logger = new Logger(MembershipSeedService.name); 11 + 12 + constructor(readonly _prisma: PrismaService) {} 13 + 14 + async seed(prisma: PrismaService): Promise<void> { 15 + this.logger.log("Seeding memberships..."); 16 + 17 + // Get all users and organizations 18 + const [allUsers, allOrganizations] = await Promise.all([ 19 + prisma["user"].findMany(), 20 + prisma["organization"].findMany(), 21 + ]); 22 + 23 + if (allUsers.length === 0 || allOrganizations.length === 0) { 24 + this.logger.warn( 25 + "No users or organizations found, skipping membership seeding", 26 + ); 27 + return; 28 + } 29 + 30 + // First, ensure test@test.test is in multiple organizations 31 + await this.ensureTestUserMemberships(prisma, allUsers, allOrganizations); 32 + 33 + // Then assign other users to organizations 34 + await Promise.all( 35 + allUsers.map(async (user) => { 36 + // Skip test@test.test as it's already handled above 37 + if (user.email === "test@test.test") { 38 + return; 39 + } 40 + 41 + // Each user should have 1-2 organizations, usually at least one 42 + const numOrganizations = faker.number.int({ min: 1, max: 2 }); 43 + const selectedOrganizations = faker.helpers.arrayElements( 44 + allOrganizations, 45 + { 46 + min: 1, 47 + max: numOrganizations, 48 + }, 49 + ); 50 + 51 + // Create memberships for this user 52 + await Promise.all( 53 + selectedOrganizations.map(async (org) => { 54 + // Check if user is already in this organization 55 + const existingMembership = await prisma["membership"].findFirst({ 56 + where: { 57 + userId: user.id, 58 + organizationId: org.id, 59 + }, 60 + }); 61 + 62 + if (!existingMembership) { 63 + await prisma["membership"].create({ 64 + data: { 65 + userId: user.id, 66 + organizationId: org.id, 67 + organizationRoleId: "member_role_id", 68 + }, 69 + }); 70 + } 71 + }), 72 + ); 73 + }), 74 + ); 75 + 76 + this.logger.log(`Assigned users to organizations`); 77 + } 78 + 79 + async ensureTestUserMemberships( 80 + prisma: PrismaService, 81 + allUsers: Array<{ id: string; email: string }>, 82 + allOrganizations: Array<{ id: string }>, 83 + ): Promise<void> { 84 + const testUser = allUsers.find((user) => user.email === "test@test.test"); 85 + 86 + if (!testUser) { 87 + this.logger.warn( 88 + "Test user not found, skipping test user membership assignment", 89 + ); 90 + return; 91 + } 92 + 93 + // Add test@test.test to the first 5 organizations 94 + const organizationsForTestUser = allOrganizations.slice(0, 5); 95 + 96 + await Promise.all( 97 + organizationsForTestUser.map(async (org) => { 98 + // Check if user is already in this organization 99 + const existingMembership = await prisma["membership"].findFirst({ 100 + where: { 101 + userId: testUser.id, 102 + organizationId: org.id, 103 + }, 104 + }); 105 + 106 + if (!existingMembership) { 107 + this.logger.log(`Adding test@test.test to organization ${org.id}`); 108 + await prisma["membership"].create({ 109 + data: { 110 + userId: testUser.id, 111 + organizationId: org.id, 112 + organizationRoleId: "member_role_id", 113 + }, 114 + }); 115 + } 116 + }), 117 + ); 118 + 119 + this.logger.log( 120 + `Test user added to ${organizationsForTestUser.length} organizations`, 121 + ); 122 + } 123 + }
+57
apps/server/src/modules/organization/seed/organization.seed.ts
··· 1 + import { faker } from "@faker-js/faker"; 2 + import { Injectable, Logger } from "@nestjs/common"; 3 + import { PrismaService } from "@/modules/database/prisma.service"; 4 + import { Seeder } from "@/modules/database/seed/seed.service"; 5 + import { Seeder as SeederDecorator } from "@/modules/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 + }
+88
apps/server/src/modules/user/seed/user.seed.ts
··· 1 + import { Injectable, Logger } from "@nestjs/common"; 2 + import { PrismaService } from "@/modules/database/prisma.service"; 3 + import { Seeder } from "@/modules/database/seed/seed.service"; 4 + import { Seeder as SeederDecorator } from "@/modules/database/seed/seeder.decorator"; 5 + 6 + @Injectable() 7 + @SeederDecorator("Users") 8 + export class UserSeedService implements Seeder { 9 + private readonly logger = new Logger(UserSeedService.name); 10 + 11 + constructor(private readonly prisma: PrismaService) {} 12 + 13 + async seed(prisma: PrismaService): Promise<void> { 14 + this.logger.log("Seeding users..."); 15 + await this.ensureTestUser(prisma); 16 + await this.ensureAdditionalTestUsers(prisma); 17 + } 18 + 19 + async ensureTestUser( 20 + prisma: PrismaService = this.prisma, 21 + ): Promise<{ id: string; email: string; name: string }> { 22 + let testUser = await prisma["user"].findUnique({ 23 + where: { email: "test@test.test" }, 24 + }); 25 + 26 + if (!testUser) { 27 + this.logger.log("Creating test user..."); 28 + testUser = await prisma["user"].create({ 29 + data: { 30 + email: "test@test.test", 31 + name: "Test User", 32 + // bcrypt hash for "password" 33 + password: 34 + "$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", 35 + }, 36 + }); 37 + } 38 + 39 + return { id: testUser.id, email: testUser.email, name: testUser.name }; 40 + } 41 + 42 + async ensureAdditionalTestUsers( 43 + prisma: PrismaService = this.prisma, 44 + ): Promise<Array<{ id: string; email: string; name: string }>> { 45 + const testUsers = [ 46 + { email: "alice@example.com", name: "Alice Johnson" }, 47 + { email: "bob@example.com", name: "Bob Smith" }, 48 + { email: "carol@example.com", name: "Carol Davis" }, 49 + { email: "david@example.com", name: "David Wilson" }, 50 + { email: "eve@example.com", name: "Eve Brown" }, 51 + { email: "frank@example.com", name: "Frank Miller" }, 52 + { email: "grace@example.com", name: "Grace Taylor" }, 53 + { email: "henry@example.com", name: "Henry Anderson" }, 54 + { email: "ivy@example.com", name: "Ivy Thomas" }, 55 + { email: "jack@example.com", name: "Jack Jackson" }, 56 + { email: "karen@example.com", name: "Karen White" }, 57 + { email: "leo@example.com", name: "Leo Harris" }, 58 + { email: "mary@example.com", name: "Mary Martin" }, 59 + { email: "nick@example.com", name: "Nick Garcia" }, 60 + { email: "olivia@example.com", name: "Olivia Martinez" }, 61 + ]; 62 + 63 + const createdUsers = await Promise.all( 64 + testUsers.map(async (userData) => { 65 + let user = await prisma["user"].findUnique({ 66 + where: { email: userData.email }, 67 + }); 68 + 69 + if (!user) { 70 + this.logger.log(`Creating test user: ${userData.name}`); 71 + user = await prisma["user"].create({ 72 + data: { 73 + email: userData.email, 74 + name: userData.name, 75 + // bcrypt hash for "password" 76 + password: 77 + "$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", 78 + }, 79 + }); 80 + } 81 + 82 + return { id: user.id, email: user.email, name: user.name }; 83 + }), 84 + ); 85 + 86 + return createdUsers; 87 + } 88 + }
+18
apps/server/src/modules/user/user.entity.ts
··· 1 + import { BaseEntity } from "@/modules/base/base.entity"; 2 + 3 + export class User extends BaseEntity { 4 + email: string; 5 + name: string; 6 + 7 + constructor( 8 + id: string, 9 + email: string, 10 + name: string, 11 + createdAt: Date, 12 + updatedAt: Date, 13 + ) { 14 + super(id, createdAt, updatedAt); 15 + this.email = email; 16 + this.name = name; 17 + } 18 + }
+37
apps/server/src/modules/user/user.mapper.ts
··· 1 + import { Injectable } from "@nestjs/common"; 2 + import type { User as PrismaUser } from "@prisma/client"; 3 + import type { BaseMapper } from "@/modules/base/mapper.interface"; 4 + import { User } from "./user.entity"; 5 + 6 + /** 7 + * Mapper service for converting between Prisma User entities and domain User entities 8 + */ 9 + @Injectable() 10 + export class UserMapper implements BaseMapper<PrismaUser, User> { 11 + /** 12 + * Maps a Prisma User entity to a domain User entity 13 + * Uses overloads to return the correct type based on input 14 + */ 15 + toDomain(prismaUser: null): null; 16 + toDomain(prismaUser: PrismaUser): User; 17 + toDomain(prismaUser: PrismaUser | null): User | null; 18 + toDomain(prismaUser: PrismaUser | null): User | null { 19 + if (prismaUser === null) { 20 + return null; 21 + } 22 + return new User( 23 + prismaUser.id, 24 + prismaUser.email, 25 + prismaUser.name, 26 + prismaUser.createdAt, 27 + prismaUser.updatedAt, 28 + ); 29 + } 30 + 31 + /** 32 + * Maps an array of Prisma User entities to domain User entities 33 + */ 34 + mapToDomain(prismaUsers: PrismaUser[]): User[] { 35 + return prismaUsers.map((user) => this.toDomain(user)); 36 + } 37 + }
+12
apps/server/src/modules/user/user.module.ts
··· 1 + import { Module } from "@nestjs/common"; 2 + import { DatabaseModule } from "@/modules/database/database.module"; 3 + import { UserSeedService } from "./seed/user.seed"; 4 + import { UserMapper } from "./user.mapper"; 5 + import { UserService } from "./user.service"; 6 + 7 + @Module({ 8 + imports: [DatabaseModule], 9 + providers: [UserService, UserMapper, UserSeedService], 10 + exports: [UserService, UserMapper], 11 + }) 12 + export class UserModule {}
+72
apps/server/src/modules/user/user.service.ts
··· 1 + import { Injectable } from "@nestjs/common"; 2 + import { notFound } from "@/modules/base/not-found.util"; 3 + import { PrismaService } from "@/modules/database/prisma.service"; 4 + import { User } from "./user.entity"; 5 + import { UserMapper } from "./user.mapper"; 6 + 7 + @Injectable() 8 + export class UserService { 9 + constructor( 10 + private prisma: PrismaService, 11 + private userMapper: UserMapper, 12 + ) {} 13 + 14 + async create(email: string, name: string, password: string): Promise<User> { 15 + const prismaUser = await this.prisma["user"].create({ 16 + data: { 17 + email, 18 + name, 19 + password, 20 + }, 21 + }); 22 + 23 + return this.userMapper.toDomain(prismaUser); 24 + } 25 + 26 + async findByEmail(email: string): Promise<User | null> { 27 + const prismaUser = await this.prisma["user"].findUnique({ 28 + where: { email }, 29 + }); 30 + 31 + return this.userMapper.toDomain(prismaUser); 32 + } 33 + 34 + async findById(id: string): Promise<User | null> { 35 + const prismaUser = await this.prisma["user"].findUnique({ 36 + where: { id }, 37 + }); 38 + 39 + return this.userMapper.toDomain(prismaUser); 40 + } 41 + 42 + async exists(email: string): Promise<boolean> { 43 + const user = await this.prisma["user"].findUnique({ 44 + where: { email }, 45 + select: { id: true }, 46 + }); 47 + 48 + return user !== null; 49 + } 50 + 51 + /** 52 + * Gets user password hash for authentication (Prisma level only) 53 + */ 54 + async getPasswordHash(email: string): Promise<string | null> { 55 + const prismaUser = await this.prisma["user"].findUnique({ 56 + where: { email }, 57 + select: { password: true }, 58 + }); 59 + 60 + return prismaUser?.password ?? null; 61 + } 62 + 63 + async findByEmailOrFail(email: string): Promise<User> { 64 + const user = await this.findByEmail(email); 65 + return user ?? notFound("User", "email", email); 66 + } 67 + 68 + async findByIdOrFail(id: string): Promise<User> { 69 + const user = await this.findById(id); 70 + return user ?? notFound("User", "id", id); 71 + } 72 + }
+68
apps/server/src/modules/user/user.type.ts
··· 1 + import { Field, ID, ObjectType } from "@nestjs/graphql"; 2 + import { ApplicationConnection } from "@/modules/application/graphql/application.type"; 3 + import { CVConnection } from "@/modules/cv-template/graphql/cv-connection.type"; 4 + import { EducationConnection } from "@/modules/education/graphql/education-connection.type"; 5 + import { UserJobExperienceConnection } from "@/modules/job-experience/employment/graphql/user-job-experience-connection.type"; 6 + import { Organization } from "@/modules/organization/organization.entity"; 7 + import type { User as DomainUser } from "@/modules/user/user.entity"; 8 + import { VacancyConnection } from "@/modules/vacancies/graphql/vacancy-connection.type"; 9 + 10 + @ObjectType() 11 + export class User { 12 + @Field(() => ID) 13 + id: string; 14 + 15 + @Field() 16 + email: string; 17 + 18 + @Field() 19 + name: string; 20 + 21 + @Field(() => Date) 22 + createdAt: Date; 23 + 24 + @Field(() => [Organization], { nullable: true }) 25 + organizations?: Organization[] | undefined; 26 + 27 + @Field(() => UserJobExperienceConnection, { nullable: true }) 28 + experience?: UserJobExperienceConnection | undefined; 29 + 30 + @Field(() => EducationConnection, { nullable: true }) 31 + educationHistory?: EducationConnection | undefined; 32 + 33 + @Field(() => ApplicationConnection, { nullable: true }) 34 + applications?: ApplicationConnection | undefined; 35 + 36 + @Field(() => VacancyConnection, { nullable: true }) 37 + vacancies?: VacancyConnection | undefined; 38 + 39 + @Field(() => CVConnection, { nullable: true }) 40 + cvs?: CVConnection | undefined; 41 + 42 + constructor( 43 + id: string, 44 + email: string, 45 + name: string, 46 + createdAt: Date, 47 + organizations?: Organization[] | undefined, 48 + ) { 49 + this.id = id; 50 + this.email = email; 51 + this.name = name; 52 + this.createdAt = createdAt; 53 + this.organizations = organizations ?? undefined; 54 + } 55 + 56 + static fromDomain( 57 + domainUser: DomainUser, 58 + organizations?: Organization[], 59 + ): User { 60 + return new User( 61 + domainUser.id, 62 + domainUser.email, 63 + domainUser.name, 64 + domainUser.createdAt, 65 + organizations, 66 + ); 67 + } 68 + }
+93
apps/server/src/modules/vacancies/seed/vacancy.seed.ts
··· 1 + import { faker } from "@faker-js/faker"; 2 + import { Injectable, Logger } from "@nestjs/common"; 3 + import { PrismaService } from "@/modules/database/prisma.service"; 4 + import { Seeder } from "@/modules/database/seed/seed.service"; 5 + import { Seeder as SeederDecorator } from "@/modules/database/seed/seeder.decorator"; 6 + 7 + @Injectable() 8 + @SeederDecorator("Public Vacancies") 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 + location: faker.location.city(), 70 + minSalary, 71 + maxSalary, 72 + applicationUrl: faker.internet.url(), 73 + deadline: faker.date.future(), 74 + isActive: true, 75 + isPublic: true, 76 + }; 77 + }); 78 + 79 + // Create vacancies in batches 80 + const batchSize = 10; 81 + for (let i = 0; i < vacanciesToCreate.length; i += batchSize) { 82 + const batch = vacanciesToCreate.slice(i, i + batchSize); 83 + await Promise.all( 84 + batch.map((data) => prisma["vacancy"].create({ data })), 85 + ); 86 + this.logger.log( 87 + `Created ${Math.min(i + batchSize, vacanciesToCreate.length)}/${vacanciesToCreate.length} vacancies...`, 88 + ); 89 + } 90 + 91 + this.logger.log(`✅ Created ${vacanciesToCreate.length} public vacancies`); 92 + } 93 + }