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.

chore(server): update server configuration and dependencies

+129 -190
+52 -13
apps/server/Dockerfile
··· 1 1 FROM node:22-alpine 2 2 3 - # Install OpenSSL for Prisma 4 - RUN apk add openssl 3 + # ============================================================================= 4 + # Layer 1: System dependencies (rarely changes) 5 + # ============================================================================= 6 + # Install OpenSSL and curl for Prisma and health checks 7 + RUN apk add --no-cache openssl curl 5 8 6 9 # Set working directory 7 10 WORKDIR /app 8 11 9 - # Copy package files first (for better layer caching) 12 + # ============================================================================= 13 + # Layer 2: Package dependencies (changes when dependencies update) 14 + # ============================================================================= 15 + # Copy only package.json files to leverage Docker layer caching 16 + # This layer is cached as long as no dependencies change 10 17 COPY package*.json ./ 18 + COPY lerna.json ./ 11 19 COPY apps/server/package*.json ./apps/server/ 12 - COPY packages/ ./packages/ 20 + COPY apps/client/package*.json ./apps/client/ 21 + COPY packages/utils/package.json ./packages/utils/ 22 + COPY packages/tsconfig/package.json ./packages/tsconfig/ 23 + COPY packages/biome-config/package.json ./packages/biome-config/ 24 + COPY packages/ui/package.json ./packages/ui/ 25 + 26 + # Install all dependencies (monorepo) 27 + # This is the most expensive layer, so we want it cached 28 + RUN npm install --ignore-scripts 29 + 30 + # ============================================================================= 31 + # Layer 3: Configuration files (changes less frequently than source code) 32 + # ============================================================================= 33 + # Copy shared configuration packages 34 + COPY packages/tsconfig/ ./packages/tsconfig/ 35 + COPY packages/biome-config/ ./packages/biome-config/ 36 + COPY packages/utils/ ./packages/utils/ 37 + COPY packages/ui/ ./packages/ui/ 38 + 39 + # ============================================================================= 40 + # Layer 4: Prisma schema and generation (changes when schema updates) 41 + # ============================================================================= 42 + # Copy Prisma schema 43 + COPY apps/server/prisma/ ./apps/server/prisma/ 13 44 14 - # Install dependencies 15 - RUN npm install 45 + # Generate Prisma client 46 + # This layer is cached unless the Prisma schema changes 47 + RUN npx prisma generate --schema=apps/server/prisma/schema.prisma 16 48 17 - # Install server dependencies specifically 18 - WORKDIR /app/apps/server 19 - RUN npm install 49 + # ============================================================================= 50 + # Layer 5: Source code (changes most frequently) 51 + # ============================================================================= 52 + # Copy server source code 53 + # This layer changes on any code change but comes last for optimal caching 54 + COPY apps/server/src/ ./apps/server/src/ 55 + COPY apps/server/tsconfig*.json ./apps/server/ 56 + COPY apps/server/test/ ./apps/server/test/ 20 57 21 - # Copy source code (this layer will be invalidated on code changes) 22 - COPY . . 58 + # Copy scripts for health checks 59 + COPY scripts/ ./scripts/ 23 60 24 - # Generate Prisma client (only runs if schema changed) 61 + # ============================================================================= 62 + # Runtime configuration 63 + # ============================================================================= 64 + # Set working directory to server 25 65 WORKDIR /app/apps/server 26 - RUN npm run prisma:generate 27 66 28 67 # Expose port 29 68 EXPOSE 3000
+12 -7
apps/server/package.json
··· 8 8 "start": "node dist/main.js", 9 9 "dev": "nodemon --watch src -e ts --exec \"ts-node -r tsconfig-paths/register src/main.ts\"", 10 10 "lint": "biome check .", 11 + "lint:fix": "biome check --write .", 11 12 "test": "jest --config ./test/jest-unit.json", 12 13 "test:unit": "jest --config ./test/jest-unit.json", 13 14 "test:e2e": "jest --config ./test/jest-e2e.json", ··· 16 17 "e2e:coverage:c8": "E2E_EXTERNAL_URL=http://localhost:3000 npm run test:e2e && npm run e2e:coverage:c8:report", 17 18 "e2e:coverage:c8:stop": "pkill -f 'node dist/main.js' || true", 18 19 "e2e:coverage:all": "node src/scripts/e2e-coverage-all.cjs", 19 - "prisma:generate": "prisma generate --schema=./prisma/schema.prisma", 20 - "prisma:migrate": "prisma migrate dev --schema=./prisma/schema.prisma", 21 - "prisma:deploy": "prisma migrate deploy --schema=./prisma/schema.prisma", 22 - "prisma:studio": "prisma studio --schema=./prisma/schema.prisma", 20 + "prisma:generate": "prisma generate", 21 + "prisma:migrate": "prisma migrate dev", 22 + "prisma:deploy": "prisma migrate deploy", 23 + "prisma:studio": "prisma studio", 23 24 "seed": "ts-node -r tsconfig-paths/register src/scripts/seed.ts", 24 25 "seed:test": "ts-node -r tsconfig-paths/register src/scripts/seed-test.ts" 25 26 }, 26 27 "dependencies": { 27 - "@cv/utils": "^0.0.0", 28 + "@cv/utils": "*", 28 29 "@faker-js/faker": "^10.1.0", 29 30 "@nestjs/apollo": "^12.2.2", 30 31 "@nestjs/common": "^10.4.7", ··· 32 33 "@nestjs/core": "^10.4.7", 33 34 "@nestjs/graphql": "^12.2.2", 34 35 "@nestjs/jwt": "^10.2.0", 36 + "@nestjs/mapped-types": "^2.1.0", 35 37 "@nestjs/passport": "^10.0.3", 36 38 "@nestjs/platform-express": "^10.4.7", 37 39 "@prisma/client": "^6.17.1", ··· 40 42 "class-transformer": "^0.5.1", 41 43 "class-validator": "^0.14.0", 42 44 "graphql": "^16.11.0", 45 + "graphql-scalars": "^1.23.0", 46 + "graphql-type-json": "^0.3.2", 47 + "joi": "^17.13.3", 43 48 "nestjs-zod": "^3.0.0", 44 49 "passport": "^0.7.0", 45 50 "passport-jwt": "^4.0.1", ··· 50 55 }, 51 56 "devDependencies": { 52 57 "@biomejs/biome": "^2.2.6", 53 - "@cv/biome-config": "0.0.0", 54 - "@cv/tsconfig": "0.0.0", 58 + "@cv/biome-config": "*", 59 + "@cv/tsconfig": "*", 55 60 "@nestjs/testing": "^10.4.7", 56 61 "@types/bcryptjs": "^2.4.6", 57 62 "@types/jest": "^29.5.12",
+6
apps/server/prisma.config.ts
··· 1 + import path from "node:path"; 2 + import type { PrismaConfig } from "prisma"; 3 + 4 + export default { 5 + schema: path.join(__dirname, "prisma"), 6 + } satisfies PrismaConfig;
+1 -163
apps/server/prisma/schema.prisma
··· 4 4 generator client { 5 5 provider = "prisma-client-js" 6 6 enableTracing = false 7 + binaryTargets = ["native", "darwin-arm64", "linux-musl-arm64-openssl-3.0.x"] 7 8 } 8 9 9 10 datasource db { 10 11 provider = "postgresql" 11 12 url = env("DATABASE_URL") 12 13 } 13 - 14 - model User { 15 - id String @id @default(cuid()) 16 - email String @unique 17 - name String 18 - password String 19 - createdAt DateTime @default(now()) 20 - updatedAt DateTime @updatedAt 21 - 22 - // Job experiences 23 - jobExperiences UserJobExperience[] 24 - 25 - // Organizations 26 - organizations UserOrganization[] 27 - 28 - // Vacancies 29 - vacancies Vacancy[] 30 - 31 - @@map("users") 32 - } 33 - 34 - model Skill { 35 - id String @id @default(cuid()) 36 - name String @unique 37 - description String? 38 - createdAt DateTime @default(now()) 39 - updatedAt DateTime @updatedAt 40 - 41 - // Job experiences that use this skill 42 - jobExperiences UserJobExperience[] 43 - 44 - @@map("skills") 45 - } 46 - 47 - model Company { 48 - id String @id @default(cuid()) 49 - name String @unique 50 - description String? 51 - website String? 52 - createdAt DateTime @default(now()) 53 - updatedAt DateTime @updatedAt 54 - 55 - // Job experiences at this company 56 - jobExperiences UserJobExperience[] 57 - 58 - @@map("companies") 59 - } 60 - 61 - model Role { 62 - id String @id @default(cuid()) 63 - name String @unique 64 - description String? 65 - createdAt DateTime @default(now()) 66 - updatedAt DateTime @updatedAt 67 - 68 - // Job experiences with this role 69 - jobExperiences UserJobExperience[] 70 - 71 - @@map("roles") 72 - } 73 - 74 - model Level { 75 - id String @id @default(cuid()) 76 - name String @unique 77 - description String? 78 - createdAt DateTime @default(now()) 79 - updatedAt DateTime @updatedAt 80 - 81 - // Job experiences at this level 82 - jobExperiences UserJobExperience[] 83 - 84 - @@map("levels") 85 - } 86 - 87 - model UserJobExperience { 88 - id String @id @default(cuid()) 89 - userId String 90 - companyId String 91 - roleId String 92 - levelId String 93 - startDate DateTime 94 - endDate DateTime? 95 - description String? 96 - createdAt DateTime @default(now()) 97 - updatedAt DateTime @updatedAt 98 - 99 - // Relations 100 - user User @relation(fields: [userId], references: [id], onDelete: Cascade) 101 - company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) 102 - role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) 103 - level Level @relation(fields: [levelId], references: [id], onDelete: Cascade) 104 - 105 - // Skills used in this job experience 106 - skills Skill[] 107 - 108 - @@map("user_job_experiences") 109 - } 110 - 111 - model Organization { 112 - id String @id @default(cuid()) 113 - name String @unique 114 - description String? 115 - createdAt DateTime @default(now()) 116 - updatedAt DateTime @updatedAt 117 - 118 - // Users in this organization 119 - users UserOrganization[] 120 - 121 - @@map("organizations") 122 - } 123 - 124 - model OrganizationRole { 125 - id String @id @default(cuid()) 126 - name String @unique 127 - description String? 128 - color String @default("#6366f1") 129 - createdAt DateTime @default(now()) 130 - updatedAt DateTime @updatedAt 131 - 132 - // Users with this role in organizations 133 - userOrganizations UserOrganization[] 134 - 135 - @@map("organization_roles") 136 - } 137 - 138 - model UserOrganization { 139 - id String @id @default(cuid()) 140 - userId String 141 - organizationId String 142 - organizationRoleId String 143 - createdAt DateTime @default(now()) 144 - updatedAt DateTime @updatedAt 145 - 146 - // Relations 147 - user User @relation(fields: [userId], references: [id], onDelete: Cascade) 148 - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) 149 - role OrganizationRole @relation(fields: [organizationRoleId], references: [id], onDelete: Cascade) 150 - 151 - @@unique([userId, organizationId]) 152 - @@map("user_organizations") 153 - } 154 - 155 - model Vacancy { 156 - id String @id @default(cuid()) 157 - userId String 158 - title String 159 - company String 160 - description String? 161 - requirements String? 162 - location String? 163 - salary String? 164 - jobType String? 165 - applicationUrl String? 166 - deadline DateTime? 167 - isActive Boolean @default(true) 168 - createdAt DateTime @default(now()) 169 - updatedAt DateTime @updatedAt 170 - 171 - // Relations 172 - user User @relation(fields: [userId], references: [id], onDelete: Cascade) 173 - 174 - @@map("vacancies") 175 - }
+41 -6
apps/server/src/main.ts
··· 10 10 11 11 // Enable CORS 12 12 const configuredOrigin = configService.get<string>("CLIENT_ORIGIN"); 13 + const configuredDocsOrigin = configService.get<string>("DOCS_ORIGIN"); 14 + const nodeEnv = configService.get<string>("NODE_ENV") || "development"; 15 + const isDevelopment = nodeEnv === "development"; 16 + 13 17 const allowedOrigins = [ 14 18 configuredOrigin, 15 - "http://localhost:5173", 16 - "http://127.0.0.1:5173", 19 + configuredDocsOrigin, 20 + "http://localhost:5173", // Client app 21 + "http://127.0.0.1:5173", // Client app (localhost) 22 + "http://localhost:3001", // Docs app 23 + "http://127.0.0.1:3001", // Docs app (localhost) 24 + "http://localhost:3000", // GraphQL Playground 25 + "http://127.0.0.1:3000", // GraphQL Playground (localhost) 17 26 ].filter(Boolean) as string[]; 18 27 19 28 app.enableCors({ 20 29 origin: (origin, callback) => { 21 30 // Allow non-browser or same-origin requests with no Origin header 22 - if (!origin) return callback(null, true); 23 - if (allowedOrigins.includes(origin)) return callback(null, true); 31 + // This includes requests from server-to-server, Postman, etc. 32 + if (!origin) { 33 + return callback(null, true); 34 + } 35 + 36 + // In development, allow any localhost origin 37 + if (isDevelopment) { 38 + const isLocalhost = 39 + /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?$/.test( 40 + origin, 41 + ); 42 + if (isLocalhost) { 43 + return callback(null, true); 44 + } 45 + } 46 + 47 + // Check if origin is in allowed list 48 + if (allowedOrigins.includes(origin)) { 49 + return callback(null, true); 50 + } 51 + 24 52 return callback(new Error("Not allowed by CORS"), false); 25 53 }, 26 54 credentials: true, 27 - methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], 28 - allowedHeaders: ["Content-Type", "Authorization"], 55 + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"], 56 + allowedHeaders: [ 57 + "Content-Type", 58 + "Authorization", 59 + "Accept", 60 + "Origin", 61 + "X-Requested-With", 62 + ], 63 + exposedHeaders: ["Content-Type", "Authorization"], 29 64 }); 30 65 31 66 app.useGlobalPipes(new ZodValidationPipe());
+17 -1
apps/server/src/modules/app.module.ts
··· 2 2 import { Module } from "@nestjs/common"; 3 3 import { ConfigModule } from "@nestjs/config"; 4 4 import { GraphQLModule } from "@nestjs/graphql"; 5 + import { AppConfigModule } from "@/config/config.module"; 6 + import { envValidationSchema } from "@/config/env.validation"; 5 7 import { AppModule as AppModuleComponent } from "./app/app.module"; 8 + import { ApplicationModule } from "./application/application.module"; 6 9 import { AuthModule } from "./auth/auth.module"; 7 10 import { BaseModule } from "./base/base.module"; 11 + import { CVTemplateModule } from "./cv-template/cv-template.module"; 8 12 import { DatabaseModule } from "./database/database.module"; 13 + import { SeedModule } from "./database/seed/seed.module"; 14 + import { EducationModule } from "./education/education.module"; 9 15 import { CompanyModule } from "./job-experience/company/company.module"; 10 16 import { EmploymentModule } from "./job-experience/employment/employment.module"; 11 17 import { LevelModule } from "./job-experience/level/level.module"; 12 18 import { RoleModule } from "./job-experience/role/role.module"; 13 19 import { SkillModule } from "./job-experience/skill/skill.module"; 14 20 import { OrganizationModule } from "./organization/organization.module"; 15 - import { SeedModule } from "./seed/seed.module"; 21 + import { UserModule } from "./user/user.module"; 16 22 import { VacancyModule } from "./vacancies/vacancy.module"; 17 23 18 24 @Module({ 19 25 imports: [ 20 26 ConfigModule.forRoot({ 21 27 isGlobal: true, 28 + validationSchema: envValidationSchema, 29 + validationOptions: { 30 + abortEarly: false, // Show all validation errors at once 31 + allowUnknown: true, // Allow unknown environment variables 32 + }, 22 33 }), 23 34 GraphQLModule.forRoot<ApolloDriverConfig>({ 24 35 driver: ApolloDriver, 25 36 autoSchemaFile: true, 26 37 sortSchema: true, 27 38 }), 39 + AppConfigModule, 28 40 BaseModule, 29 41 DatabaseModule, 30 42 AuthModule, 43 + UserModule, 31 44 AppModuleComponent, 32 45 SkillModule, 33 46 CompanyModule, ··· 36 49 EmploymentModule, 37 50 OrganizationModule, 38 51 VacancyModule, 52 + ApplicationModule, 53 + CVTemplateModule, 54 + EducationModule, 39 55 SeedModule, 40 56 ], 41 57 providers: [],