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-36): inline templates into tsup bundle, add unique constraint on cv_templates.name

+54 -41
-1
.docker/.manifests/apps__server__package.json
··· 5 5 "type": "commonjs", 6 6 "scripts": { 7 7 "build": "tsup", 8 - "postbuild": "sh scripts/post-build.sh", 9 8 "start": "node dist/main.js", 10 9 "dev": "nodemon --watch src -e ts --exec \"ts-node -r tsconfig-paths/register src/main.ts\"", 11 10 "lint": "biome check .",
-1
apps/server/package.json
··· 5 5 "type": "commonjs", 6 6 "scripts": { 7 7 "build": "tsup", 8 - "postbuild": "sh scripts/post-build.sh", 9 8 "start": "node dist/main.js", 10 9 "dev": "nodemon --watch src -e ts --exec \"ts-node -r tsconfig-paths/register src/main.ts\"", 11 10 "lint": "biome check .",
+2
apps/server/prisma/migrations/20260412000000_add_unique_cv_template_name/migration.sql
··· 1 + -- CreateIndex 2 + CREATE UNIQUE INDEX "cv_templates_name_key" ON "cv_templates"("name");
+1 -1
apps/server/prisma/models/cv.prisma
··· 1 1 model CVTemplate { 2 2 id String @id @default(cuid()) 3 - name String 3 + name String @unique 4 4 description String? 5 5 body String? @db.Text 6 6 css String? @db.Text
-7
apps/server/scripts/post-build.sh
··· 1 - #!/bin/sh 2 - set -eu 3 - 4 - DIST_DIR="dist" 5 - 6 - mkdir -p "${DIST_DIR}/templates" 7 - cp src/modules/cv-template/seed/templates/* "${DIST_DIR}/templates/"
+23 -30
apps/server/src/modules/cv-template/seed/cv-template.seed.ts
··· 1 - import { readFileSync } from "node:fs"; 2 - import { join } from "node:path"; 3 1 import { PrismaService } from "@cv/system"; 4 2 import { Injectable, Logger } from "@nestjs/common"; 5 3 import { Seeder } from "@/modules/database/seed/seed.service"; 6 4 import { Seeder as SeederDecorator } from "@/modules/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 - }); 5 + import classicExecutiveBody from "./templates/classic-executive.hbs"; 6 + import classicExecutiveCss from "./templates/classic-executive.styles"; 7 + import creativePortfolioBody from "./templates/creative-portfolio.hbs"; 8 + import creativePortfolioCss from "./templates/creative-portfolio.styles"; 9 + import modernProfessionalBody from "./templates/modern-professional.hbs"; 10 + import modernProfessionalCss from "./templates/modern-professional.styles"; 14 11 15 12 interface TemplateData { 16 13 name: string; ··· 24 21 { 25 22 name: "Modern Professional", 26 23 description: "A clean, modern template perfect for tech professionals", 27 - ...readTemplate("modern-professional"), 24 + body: modernProfessionalBody, 25 + css: modernProfessionalCss, 28 26 engine: "handlebars", 29 27 }, 30 28 { 31 29 name: "Classic Executive", 32 30 description: "A traditional template suitable for executive positions", 33 - ...readTemplate("classic-executive"), 31 + body: classicExecutiveBody, 32 + css: classicExecutiveCss, 34 33 engine: "handlebars", 35 34 }, 36 35 { 37 36 name: "Creative Portfolio", 38 37 description: "A creative template for designers and artists", 39 - ...readTemplate("creative-portfolio"), 38 + body: creativePortfolioBody, 39 + css: creativePortfolioCss, 40 40 engine: "handlebars", 41 41 }, 42 42 ]; ··· 52 52 this.logger.log("Seeding CV templates..."); 53 53 54 54 await Promise.all( 55 - templates.map(async (template) => { 56 - const existing = await prisma["cVTemplate"].findFirst({ 55 + templates.map((template) => 56 + prisma["cVTemplate"].upsert({ 57 57 where: { name: template.name }, 58 - }); 59 - 60 - if (existing) { 61 - await prisma["cVTemplate"].update({ 62 - where: { id: existing.id }, 63 - data: { 64 - description: template.description, 65 - body: template.body, 66 - css: template.css, 67 - engine: template.engine, 68 - }, 69 - }); 70 - } else { 71 - await prisma["cVTemplate"].create({ data: template }); 72 - } 73 - }), 58 + update: { 59 + description: template.description, 60 + body: template.body, 61 + css: template.css, 62 + engine: template.engine, 63 + }, 64 + create: template, 65 + }), 66 + ), 74 67 ); 75 68 } 76 69 }
apps/server/src/modules/cv-template/seed/templates/classic-executive.css apps/server/src/modules/cv-template/seed/templates/classic-executive.styles
apps/server/src/modules/cv-template/seed/templates/creative-portfolio.css apps/server/src/modules/cv-template/seed/templates/creative-portfolio.styles
apps/server/src/modules/cv-template/seed/templates/modern-professional.css apps/server/src/modules/cv-template/seed/templates/modern-professional.styles
+9
apps/server/src/types/assets.d.ts
··· 1 + declare module "*.hbs" { 2 + const content: string; 3 + export default content; 4 + } 5 + 6 + declare module "*.styles" { 7 + const content: string; 8 + export default content; 9 + }
+19 -1
apps/server/tsup.config.ts
··· 1 + import { readFileSync } from "node:fs"; 2 + import { resolve } from "node:path"; 3 + import type { Plugin } from "esbuild"; 1 4 import { defineConfig } from "tsup"; 2 5 6 + const textAssetPlugin: Plugin = { 7 + name: "text-assets", 8 + setup(build) { 9 + build.onResolve({ filter: /\.(hbs|styles)$/ }, (args) => ({ 10 + path: resolve(args.resolveDir, args.path), 11 + namespace: "text-asset", 12 + })); 13 + build.onLoad({ filter: /.*/, namespace: "text-asset" }, (args) => ({ 14 + contents: readFileSync(args.path, "utf-8"), 15 + loader: "text", 16 + })); 17 + }, 18 + }; 19 + 3 20 export default defineConfig({ 4 21 entry: ["src/main.ts", "src/scripts/seed.ts"], 5 22 outDir: "dist", ··· 10 27 sourcemap: true, 11 28 clean: true, 12 29 tsconfig: "tsconfig.build.json", 13 - onSuccess: "sh scripts/post-build.sh", 30 + 31 + esbuildPlugins: [textAssetPlugin], 14 32 15 33 noExternal: [/^@cv\//], 16 34