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(seed): split into User/ReferenceData/Organization services and add SeedModule script

+208 -287
+34
apps/server/src/modules/seed/organization-seed.service.ts
··· 1 + import { faker } from "@faker-js/faker"; 2 + import { Injectable, Logger } from "@nestjs/common"; 3 + import { PrismaService } from "../database/prisma.service"; 4 + 5 + @Injectable() 6 + export class OrganizationSeedService { 7 + // logger reserved for future diagnostics 8 + 9 + constructor(private readonly prisma: PrismaService) {} 10 + 11 + async ensureOrganizations(): Promise<Array<{ id: string }>> { 12 + const names = [ 13 + "TechCorp Solutions", 14 + "Innovation Labs", 15 + "Digital Dynamics", 16 + "Future Systems", 17 + ]; 18 + const created: Array<{ id: string }> = []; 19 + for (const name of names) { 20 + const existing = await this.prisma["organization"].findFirst({ where: { name } }); 21 + if (existing) { 22 + created.push({ id: existing.id }); 23 + continue; 24 + } 25 + const org = await this.prisma["organization"].create({ 26 + data: { name, description: faker.company.catchPhrase() }, 27 + }); 28 + created.push({ id: org.id }); 29 + } 30 + return created; 31 + } 32 + } 33 + 34 +
+84
apps/server/src/modules/seed/reference-data-seed.service.ts
··· 1 + import { faker } from "@faker-js/faker"; 2 + import { Injectable, Logger } from "@nestjs/common"; 3 + import { PrismaService } from "../database/prisma.service"; 4 + 5 + @Injectable() 6 + export class ReferenceDataSeedService { 7 + // logger reserved for future diagnostics 8 + 9 + constructor(private readonly prisma: PrismaService) {} 10 + 11 + async ensureSkills(): Promise<Array<{ id: string }>> { 12 + const skills = ["React", "TypeScript", "Node.js", "GraphQL", "PostgreSQL"]; 13 + const created: Array<{ id: string }> = []; 14 + for (const name of skills) { 15 + const existing = await this.prisma["skill"].findFirst({ where: { name } }); 16 + if (existing) { 17 + created.push({ id: existing.id }); 18 + continue; 19 + } 20 + const s = await this.prisma["skill"].create({ 21 + data: { name, description: faker.lorem.sentence() }, 22 + }); 23 + created.push({ id: s.id }); 24 + } 25 + return created; 26 + } 27 + 28 + async ensureCompanies(): Promise<Array<{ id: string }>> { 29 + const names = ["Google", "Microsoft", "Amazon", "Netflix"]; 30 + const created: Array<{ id: string }> = []; 31 + for (const name of names) { 32 + const existing = await this.prisma["company"].findFirst({ where: { name } }); 33 + if (existing) { 34 + created.push({ id: existing.id }); 35 + continue; 36 + } 37 + const c = await this.prisma["company"].create({ 38 + data: { name, description: faker.company.catchPhrase(), website: faker.internet.url() }, 39 + }); 40 + created.push({ id: c.id }); 41 + } 42 + return created; 43 + } 44 + 45 + async ensureRoles(): Promise<Array<{ id: string }>> { 46 + const names = [ 47 + "Software Engineer", 48 + "Senior Software Engineer", 49 + "Engineering Manager", 50 + ]; 51 + const created: Array<{ id: string }> = []; 52 + for (const name of names) { 53 + const existing = await this.prisma["role"].findFirst({ where: { name } }); 54 + if (existing) { 55 + created.push({ id: existing.id }); 56 + continue; 57 + } 58 + const r = await this.prisma["role"].create({ 59 + data: { name, description: faker.lorem.sentence() }, 60 + }); 61 + created.push({ id: r.id }); 62 + } 63 + return created; 64 + } 65 + 66 + async ensureLevels(): Promise<Array<{ id: string }>> { 67 + const names = ["Junior", "Mid-level", "Senior"]; 68 + const created: Array<{ id: string }> = []; 69 + for (const name of names) { 70 + const existing = await this.prisma["level"].findFirst({ where: { name } }); 71 + if (existing) { 72 + created.push({ id: existing.id }); 73 + continue; 74 + } 75 + const l = await this.prisma["level"].create({ 76 + data: { name, description: faker.lorem.sentence() }, 77 + }); 78 + created.push({ id: l.id }); 79 + } 80 + return created; 81 + } 82 + } 83 + 84 +
+11 -2
apps/server/src/modules/seed/seed.module.ts
··· 1 1 import { Module } from "@nestjs/common"; 2 + import { ConfigModule } from "@nestjs/config"; 2 3 import { DatabaseModule } from "../database/database.module"; 4 + import { OrganizationSeedService } from "./organization-seed.service"; 5 + import { ReferenceDataSeedService } from "./reference-data-seed.service"; 3 6 import { SeedService } from "./seed.service"; 7 + import { UserSeedService } from "./user-seed.service"; 4 8 5 9 @Module({ 6 - imports: [DatabaseModule], 7 - providers: [SeedService], 10 + imports: [ConfigModule.forRoot({ isGlobal: true }), DatabaseModule], 11 + providers: [ 12 + SeedService, 13 + ReferenceDataSeedService, 14 + UserSeedService, 15 + OrganizationSeedService, 16 + ], 8 17 exports: [SeedService], 9 18 }) 10 19 export class SeedModule {}
+33 -285
apps/server/src/modules/seed/seed.service.ts
··· 1 1 import { faker } from "@faker-js/faker"; 2 2 import { Injectable, Logger } from "@nestjs/common"; 3 3 import { PrismaService } from "../database/prisma.service"; 4 + import { OrganizationSeedService } from "./organization-seed.service"; 5 + import { ReferenceDataSeedService } from "./reference-data-seed.service"; 6 + import { UserSeedService } from "./user-seed.service"; 4 7 5 8 @Injectable() 6 9 export class SeedService { 7 10 private readonly logger = new Logger(SeedService.name); 8 11 9 - constructor(private prisma: PrismaService) {} 12 + constructor( 13 + private readonly prisma: PrismaService, 14 + private readonly users: UserSeedService, 15 + private readonly refs: ReferenceDataSeedService, 16 + private readonly orgs: OrganizationSeedService, 17 + ) {} 10 18 11 19 async seed(): Promise<void> { 12 20 this.logger.log("🌱 Starting database seeding..."); 13 21 14 22 // Find or create the test user 15 - let testUser = await this.prisma.user.findUnique({ 16 - where: { email: "test@test.test" }, 17 - }); 18 - 19 - if (!testUser) { 20 - this.logger.log("👤 Creating test user..."); 21 - testUser = await this.prisma.user.create({ 22 - data: { 23 - email: "test@test.test", 24 - name: "Test User", 25 - password: "$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", // password 26 - }, 27 - }); 28 - this.logger.log(`✅ Created test user: ${testUser.name} (${testUser.email})`); 29 - } else { 30 - this.logger.log(`✅ Found existing user: ${testUser.name} (${testUser.email})`); 31 - } 23 + const testUser = await this.users.ensureTestUser(); 32 24 33 25 // Clear existing job experiences for this user only 34 26 this.logger.log("🧹 Clearing existing job experiences for test user..."); ··· 36 28 where: { userId: testUser.id }, 37 29 }); 38 30 39 - // Create skills 40 - this.logger.log("💻 Creating skills..."); 41 - const skills = [ 42 - "React", 43 - "TypeScript", 44 - "Node.js", 45 - "Python", 46 - "Java", 47 - "C#", 48 - "Go", 49 - "Rust", 50 - "Vue.js", 51 - "Angular", 52 - "Svelte", 53 - "Next.js", 54 - "Express", 55 - "FastAPI", 56 - "Spring Boot", 57 - "Django", 58 - "Flask", 59 - "Laravel", 60 - "Ruby on Rails", 61 - "ASP.NET", 62 - "GraphQL", 63 - "REST API", 64 - "PostgreSQL", 65 - "MongoDB", 66 - "Redis", 67 - "MySQL", 68 - "Docker", 69 - "Kubernetes", 70 - "AWS", 71 - "Azure", 72 - "GCP", 73 - "Git", 74 - "CI/CD", 75 - "Testing", 76 - "Agile", 77 - "Scrum", 78 - "DevOps", 79 - "Machine Learning", 80 - "Data Science", 81 - "AI", 82 - "Blockchain", 83 - "Web3", 84 - "Mobile Development", 85 - "iOS", 86 - "Android", 87 - "Flutter", 88 - "React Native", 89 - "Swift", 90 - "Kotlin", 91 - "Dart", 92 - ]; 93 - 94 - const createdSkills = []; 95 - for (const skillName of skills) { 96 - const existingSkill = await this.prisma.skill.findFirst({ 97 - where: { name: skillName }, 98 - }); 99 - 100 - if (!existingSkill) { 101 - const skill = await this.prisma.skill.create({ 102 - data: { 103 - name: skillName, 104 - description: faker.lorem.sentence(), 105 - }, 106 - }); 107 - createdSkills.push(skill); 108 - } else { 109 - createdSkills.push(existingSkill); 110 - } 111 - } 112 - 113 - // Create companies 114 - this.logger.log("🏢 Creating companies..."); 115 - const companyNames = [ 116 - "Google", 117 - "Microsoft", 118 - "Apple", 119 - "Amazon", 120 - "Meta", 121 - "Netflix", 122 - "Spotify", 123 - "Uber", 124 - "Airbnb", 125 - "Tesla", 126 - "SpaceX", 127 - "Stripe", 128 - "Shopify", 129 - "Slack", 130 - "Discord", 131 - "GitHub", 132 - "Atlassian", 133 - "Salesforce", 134 - "Adobe", 135 - "Oracle", 136 - "IBM", 137 - "Intel", 138 - "NVIDIA", 139 - "AMD", 140 - "Zoom", 141 - "Dropbox", 142 - "Notion", 143 - "Figma", 144 - "Canva", 145 - "MongoDB", 146 - "Redis Labs", 147 - "Vercel", 148 - "Netlify", 149 - "Heroku", 150 - "DigitalOcean", 151 - "Linode", 152 - "Cloudflare", 153 - ]; 154 - 155 - const createdCompanies = []; 156 - for (const companyName of companyNames) { 157 - const existingCompany = await this.prisma.company.findFirst({ 158 - where: { name: companyName }, 159 - }); 160 - 161 - if (!existingCompany) { 162 - const company = await this.prisma.company.create({ 163 - data: { 164 - name: companyName, 165 - description: faker.company.catchPhrase(), 166 - website: faker.internet.url(), 167 - }, 168 - }); 169 - createdCompanies.push(company); 170 - } else { 171 - createdCompanies.push(existingCompany); 172 - } 173 - } 174 - 175 - // Create roles 176 - this.logger.log("👔 Creating roles..."); 177 - const roleNames = [ 178 - "Software Engineer", 179 - "Senior Software Engineer", 180 - "Staff Software Engineer", 181 - "Principal Software Engineer", 182 - "Tech Lead", 183 - "Engineering Manager", 184 - "Frontend Developer", 185 - "Backend Developer", 186 - "Full Stack Developer", 187 - "DevOps Engineer", 188 - "Site Reliability Engineer", 189 - "Data Engineer", 190 - "Machine Learning Engineer", 191 - "Data Scientist", 192 - "Product Manager", 193 - "Product Owner", 194 - "Scrum Master", 195 - "UX Designer", 196 - "UI Designer", 197 - "QA Engineer", 198 - "Test Engineer", 199 - "Security Engineer", 200 - "Cloud Architect", 201 - "Solution Architect", 202 - "Technical Writer", 203 - "Developer Advocate", 204 - ]; 205 - 206 - const createdRoles = []; 207 - for (const roleName of roleNames) { 208 - const existingRole = await this.prisma.role.findFirst({ 209 - where: { name: roleName }, 210 - }); 31 + const createdSkills = await this.refs.ensureSkills(); 211 32 212 - if (!existingRole) { 213 - const role = await this.prisma.role.create({ 214 - data: { 215 - name: roleName, 216 - description: faker.lorem.sentence(), 217 - }, 218 - }); 219 - createdRoles.push(role); 220 - } else { 221 - createdRoles.push(existingRole); 222 - } 223 - } 33 + const createdCompanies = await this.refs.ensureCompanies(); 224 34 225 - // Create levels 226 - this.logger.log("📊 Creating levels..."); 227 - const levelNames = [ 228 - "Intern", 229 - "Junior", 230 - "Mid-level", 231 - "Senior", 232 - "Staff", 233 - "Principal", 234 - "Distinguished", 235 - ]; 35 + const createdRoles = await this.refs.ensureRoles(); 236 36 237 - const createdLevels = []; 238 - for (const levelName of levelNames) { 239 - const existingLevel = await this.prisma.level.findFirst({ 240 - where: { name: levelName }, 241 - }); 242 - 243 - if (!existingLevel) { 244 - const level = await this.prisma.level.create({ 245 - data: { 246 - name: levelName, 247 - description: faker.lorem.sentence(), 248 - }, 249 - }); 250 - createdLevels.push(level); 251 - } else { 252 - createdLevels.push(existingLevel); 253 - } 254 - } 37 + const createdLevels = await this.refs.ensureLevels(); 255 38 256 39 // Create job experiences for the test user 257 40 this.logger.log("💼 Creating job experiences for test user..."); ··· 289 72 }); 290 73 } 291 74 292 - // Create organizations 293 - this.logger.log("🏢 Creating organizations..."); 294 - const organizationNames = [ 295 - "TechCorp Solutions", 296 - "Innovation Labs", 297 - "Digital Dynamics", 298 - "Future Systems", 299 - "CodeCrafters Inc", 300 - "DataFlow Technologies", 301 - "CloudScale Systems", 302 - "AgileWorks", 303 - "DevOps Masters", 304 - "AI Innovations", 305 - "Blockchain Builders", 306 - "Mobile First Co", 307 - "Web3 Pioneers", 308 - "Startup Accelerator", 309 - "Enterprise Solutions", 310 - "Open Source Foundation", 311 - "Developer Community", 312 - "Tech Mentorship Program", 313 - "Women in Tech", 314 - "Diversity in Tech", 315 - ]; 316 - 317 - const createdOrganizations = []; 318 - for (const orgName of organizationNames) { 319 - const existingOrg = await this.prisma.organization.findFirst({ 320 - where: { name: orgName }, 321 - }); 322 - 323 - if (!existingOrg) { 324 - const organization = await this.prisma.organization.create({ 325 - data: { 326 - name: orgName, 327 - description: faker.company.catchPhrase(), 328 - }, 329 - }); 330 - createdOrganizations.push(organization); 331 - } else { 332 - createdOrganizations.push(existingOrg); 333 - } 334 - } 75 + const createdOrganizations = await this.orgs.ensureOrganizations(); 335 76 336 77 // Get all users to assign to organizations 337 78 this.logger.log("👥 Assigning users to organizations..."); ··· 340 81 for (const user of allUsers) { 341 82 // Each user should have 1-2 organizations, usually at least one 342 83 const numOrganizations = faker.number.int({ min: 1, max: 2 }); 343 - const selectedOrganizations = faker.helpers.arrayElements(createdOrganizations, { 344 - min: 1, 345 - max: numOrganizations, 346 - }); 84 + const selectedOrganizations = faker.helpers.arrayElements( 85 + createdOrganizations, 86 + { 87 + min: 1, 88 + max: numOrganizations, 89 + }, 90 + ); 347 91 348 92 for (const org of selectedOrganizations) { 349 93 // Check if user is already in this organization 350 - const existingMembership = await this.prisma.userOrganization.findFirst({ 351 - where: { 352 - userId: user.id, 353 - organizationId: org.id, 94 + const existingMembership = await this.prisma.userOrganization.findFirst( 95 + { 96 + where: { 97 + userId: user.id, 98 + organizationId: org.id, 99 + }, 354 100 }, 355 - }); 101 + ); 356 102 357 103 if (!existingMembership) { 358 104 await this.prisma.userOrganization.create({ 359 105 data: { 360 106 userId: user.id, 361 107 organizationId: org.id, 362 - organizationRoleId: 'member_role_id', 108 + organizationRoleId: "member_role_id", 363 109 }, 364 110 }); 365 111 } ··· 373 119 this.logger.log(` - ${createdRoles.length} roles available`); 374 120 this.logger.log(` - ${createdLevels.length} levels available`); 375 121 this.logger.log(` - ${numExperiences} job experiences created`); 376 - this.logger.log(` - ${createdOrganizations.length} organizations created`); 122 + this.logger.log( 123 + ` - ${createdOrganizations.length} organizations created`, 124 + ); 377 125 this.logger.log(` - ${allUsers.length} users assigned to organizations`); 378 126 } 379 127 }
+30
apps/server/src/modules/seed/user-seed.service.ts
··· 1 + import { Injectable, Logger } from "@nestjs/common"; 2 + import { PrismaService } from "../database/prisma.service"; 3 + 4 + @Injectable() 5 + export class UserSeedService { 6 + private readonly logger = new Logger(UserSeedService.name); 7 + 8 + constructor(private readonly prisma: PrismaService) {} 9 + 10 + async ensureTestUser(): Promise<{ id: string; email: string; name: string }> { 11 + let testUser = await this.prisma["user"].findUnique({ 12 + where: { email: "test@test.test" }, 13 + }); 14 + 15 + if (!testUser) { 16 + this.logger.log("Creating test user..."); 17 + testUser = await this.prisma["user"].create({ 18 + data: { 19 + email: "test@test.test", 20 + name: "Test User", 21 + // bcrypt hash for "password" 22 + password: 23 + "$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", 24 + }, 25 + }); 26 + } 27 + 28 + return { id: testUser.id, email: testUser.email, name: testUser.name }; 29 + } 30 + }
+16
apps/server/src/scripts/seed-test.ts
··· 1 + import { NestFactory } from "@nestjs/core"; 2 + import { SeedModule } from "@/modules/seed/seed.module"; 3 + import { SeedService } from "@/modules/seed/seed.service"; 4 + 5 + async function main(): Promise<void> { 6 + const app = await NestFactory.createApplicationContext(SeedModule); 7 + const seedService = app.get(SeedService); 8 + await seedService.seed(); 9 + await app.close(); 10 + } 11 + 12 + main().catch(() => { 13 + process.exitCode = 1; 14 + }); 15 + 16 +