feat: init inkl. docker configs
This commit is contained in:
17
src/pages/api/cv/[id]/delete.ts
Normal file
17
src/pages/api/cv/[id]/delete.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { deleteCV } from '../../../../lib/db';
|
||||
|
||||
export const DELETE: APIRoute = async ({ locals, params }) => {
|
||||
try {
|
||||
deleteCV(params.id!, locals.user.id);
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} catch (err: any) {
|
||||
return new Response(JSON.stringify({ error: err.message }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
};
|
||||
260
src/pages/api/cv/[id]/preview.ts
Normal file
260
src/pages/api/cv/[id]/preview.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getProfileById } from '../../../../lib/db';
|
||||
|
||||
// Returns rendered template HTML for live preview in editor
|
||||
export const POST: APIRoute = async ({ request, params, locals }) => {
|
||||
try {
|
||||
const { template, settings, profile_id } = await request.json();
|
||||
|
||||
const user = locals.user;
|
||||
if (!user) return new Response('Unauthorized', { status: 401 });
|
||||
|
||||
const profile = getProfileById(profile_id, user.id);
|
||||
if (!profile) return new Response('Profile not found', { status: 404 });
|
||||
|
||||
const html = renderTemplate(template, settings, profile.data);
|
||||
|
||||
return new Response(html, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/html' }
|
||||
});
|
||||
} catch (err: any) {
|
||||
return new Response(`<div style="color:red;padding:20px">Error: ${err.message}</div>`, {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'text/html' }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function renderTemplate(template: number, s: any, d: any): string {
|
||||
const p = d.personal || {};
|
||||
const T = s.language === 'en';
|
||||
|
||||
function esc(str: string): string {
|
||||
return (str || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function datRange(from: string, to: string, cur: boolean) {
|
||||
return `${from} – ${cur ? (T ? 'present' : 'Aktuell') : to}`;
|
||||
}
|
||||
|
||||
const baseStyles = `
|
||||
<style>
|
||||
.cv-a4 { width: 794px; min-height: 1123px; background: white; font-size: 13px; line-height: 1.4; box-shadow: 0 8px 32px rgba(0,0,0,.15); }
|
||||
</style>`;
|
||||
|
||||
if (template === 1) return baseStyles + renderT1(p, d, s, T, esc, datRange);
|
||||
if (template === 2) return baseStyles + renderT2(p, d, s, T, esc, datRange);
|
||||
if (template === 3) return baseStyles + renderT3(p, d, s, T, esc, datRange);
|
||||
if (template === 4) return baseStyles + renderT4(p, d, s, T, esc, datRange);
|
||||
return '<div>Unknown template</div>';
|
||||
}
|
||||
|
||||
function renderT1(p: any, d: any, s: any, T: boolean, esc: Function, datRange: Function): string {
|
||||
const primary = s.primaryColor || '#1B2A5E';
|
||||
const accent = s.accentColor || '#4A7BC5';
|
||||
const font = (s.fontFamily || 'Arial, sans-serif').replace(/"/g, "'");
|
||||
|
||||
const skills = (d.skills || []).map((sk: any) =>
|
||||
`<li style="font-size:10px;color:rgba(255,255,255,.85);padding:2px 0 2px 10px;position:relative;list-style:none">
|
||||
▪ ${esc(sk.name)}${sk.level ? ` <span style="opacity:.5">${'●'.repeat(sk.level)}${'○'.repeat(5-sk.level)}</span>` : ''}
|
||||
</li>`
|
||||
).join('');
|
||||
|
||||
const langs = (d.languages || []).map((l: any) =>
|
||||
`<span style="font-weight:700;color:white;font-size:10px">${esc(l.name)}: </span><span style="font-size:10px;color:rgba(255,255,255,.75)">${esc(l.level)}</span><br/>`
|
||||
).join('');
|
||||
|
||||
const exp = (d.experience || []).map((e: any) => `
|
||||
<div style="display:grid;grid-template-columns:100px 1fr;gap:8px;margin-bottom:10px">
|
||||
<div style="font-size:9.5px;color:#888">${esc(datRange(e.dateFrom||'', e.dateTo||'', e.current))}</div>
|
||||
<div>
|
||||
<div style="font-size:11px;font-weight:700;color:#1a1a1a">${esc(e.jobTitle)}</div>
|
||||
${e.company ? `<div style="font-size:10px;color:${accent}">${esc(e.company)}${e.location ? `, ${esc(e.location)}` : ''}</div>` : ''}
|
||||
${e.description ? `<div style="font-size:10px;color:#444;margin-top:3px">${esc(e.description)}</div>` : ''}
|
||||
${(e.bullets||[]).filter(Boolean).map((b: string) => `<div style="font-size:10px;color:#333;padding-left:12px">▪ ${esc(b)}</div>`).join('')}
|
||||
</div>
|
||||
</div>`).join('');
|
||||
|
||||
const edu = (d.education || []).map((e: any) => `
|
||||
<div style="display:grid;grid-template-columns:100px 1fr;gap:8px;margin-bottom:10px">
|
||||
<div style="font-size:9.5px;color:#888">${esc(datRange(e.dateFrom||'', e.dateTo||'', e.current))}</div>
|
||||
<div>
|
||||
<div style="font-size:11px;font-weight:700;color:#1a1a1a">${esc(e.degree)}</div>
|
||||
${e.school ? `<div style="font-size:10px;color:${accent}">${esc(e.school)}${e.location ? `, ${esc(e.location)}` : ''}</div>` : ''}
|
||||
${e.description ? `<div style="font-size:10px;color:#444;margin-top:3px">${esc(e.description)}</div>` : ''}
|
||||
</div>
|
||||
</div>`).join('');
|
||||
|
||||
return `
|
||||
<div class="cv-a4" style="font-family:${font}">
|
||||
<div style="display:grid;grid-template-columns:220px 1fr;min-height:1123px">
|
||||
<aside style="background:${primary};color:white;padding:0 0 24px 0">
|
||||
${s.showPhoto ? `
|
||||
<div style="padding:24px;display:flex;justify-content:center">
|
||||
${p.photo
|
||||
? `<img src="${esc(p.photo)}" style="width:130px;height:130px;border-radius:50%;object-fit:cover;border:3px solid rgba(255,255,255,.3)"/>`
|
||||
: `<div style="width:130px;height:130px;border-radius:50%;background:rgba(255,255,255,.15);display:grid;place-items:center;color:rgba(255,255,255,.5);font-size:40px">👤</div>`
|
||||
}
|
||||
</div>` : ''}
|
||||
<div style="font-size:9.5px;font-weight:700;letter-spacing:2px;padding:10px 16px 6px;border-bottom:1px solid rgba(255,255,255,.2);margin-bottom:8px;color:rgba(255,255,255,.9)">${T ? 'PERSONAL' : 'PERSÖNLICH'}</div>
|
||||
<div style="padding:0 12px 8px">
|
||||
${p.firstName ? `<div style="display:flex;gap:8px;margin-bottom:8px"><span style="font-size:11px;filter:brightness(0) invert(1)">👤</span><div><div style="font-size:7.5px;font-weight:700;letter-spacing:1px;color:rgba(255,255,255,.6)">NAME</div><div style="font-size:10px;color:rgba(255,255,255,.9)">${esc(p.firstName)} ${esc(p.lastName)}</div></div></div>` : ''}
|
||||
${p.email ? `<div style="display:flex;gap:8px;margin-bottom:8px"><span style="font-size:11px;filter:brightness(0) invert(1)">✉️</span><div><div style="font-size:7.5px;font-weight:700;letter-spacing:1px;color:rgba(255,255,255,.6)">E-MAIL</div><div style="font-size:10px;color:rgba(255,255,255,.9)">${esc(p.email)}</div></div></div>` : ''}
|
||||
${p.phone ? `<div style="display:flex;gap:8px;margin-bottom:8px"><span style="font-size:11px;filter:brightness(0) invert(1)">📞</span><div><div style="font-size:7.5px;font-weight:700;letter-spacing:1px;color:rgba(255,255,255,.6)">TELEFON</div><div style="font-size:10px;color:rgba(255,255,255,.9)">${esc(p.phone)}</div></div></div>` : ''}
|
||||
${p.birthDate ? `<div style="display:flex;gap:8px;margin-bottom:8px"><span style="font-size:11px;filter:brightness(0) invert(1)">📅</span><div><div style="font-size:7.5px;font-weight:700;letter-spacing:1px;color:rgba(255,255,255,.6)">GEBURTSDATUM</div><div style="font-size:10px;color:rgba(255,255,255,.9)">${esc(p.birthDate)}</div></div></div>` : ''}
|
||||
${p.nationality ? `<div style="display:flex;gap:8px;margin-bottom:8px"><span style="font-size:11px;filter:brightness(0) invert(1)">🏳️</span><div><div style="font-size:7.5px;font-weight:700;letter-spacing:1px;color:rgba(255,255,255,.6)">NATIONALITÄT</div><div style="font-size:10px;color:rgba(255,255,255,.9)">${esc(p.nationality)}</div></div></div>` : ''}
|
||||
</div>
|
||||
${d.languages?.length ? `
|
||||
<div style="font-size:9.5px;font-weight:700;letter-spacing:2px;padding:10px 16px 6px;border-bottom:1px solid rgba(255,255,255,.2);margin-bottom:8px;color:rgba(255,255,255,.9)">${T ? 'LANGUAGES' : 'SPRACHEN'}</div>
|
||||
<div style="padding:0 12px 8px">${langs}</div>` : ''}
|
||||
${d.skills?.length ? `
|
||||
<div style="font-size:9.5px;font-weight:700;letter-spacing:2px;padding:10px 16px 6px;border-bottom:1px solid rgba(255,255,255,.2);margin-bottom:8px;color:rgba(255,255,255,.9)">${T ? 'SKILLS' : 'KENNTNISSE'}</div>
|
||||
<ul style="padding:0 12px 8px;margin:0">${skills}</ul>` : ''}
|
||||
${d.interests?.length ? `
|
||||
<div style="font-size:9.5px;font-weight:700;letter-spacing:2px;padding:10px 16px 6px;border-bottom:1px solid rgba(255,255,255,.2);margin-bottom:8px;color:rgba(255,255,255,.9)">${T ? 'INTERESTS' : 'INTERESSEN'}</div>
|
||||
<ul style="padding:0 12px 8px;margin:0">${d.interests.map((i: string) => `<li style="font-size:10px;color:rgba(255,255,255,.85);padding:2px 0;list-style:none">▪ ${esc(i)}</li>`).join('')}</ul>` : ''}
|
||||
</aside>
|
||||
<main style="padding:28px">
|
||||
<div style="font-size:28px;font-weight:700;color:${primary}">${esc(p.firstName)} ${esc(p.lastName)}</div>
|
||||
${p.jobTitle ? `<div style="font-size:11px;color:#666;letter-spacing:1px;text-transform:uppercase;margin-bottom:16px">${esc(p.jobTitle)}</div>` : ''}
|
||||
${d.profile ? `
|
||||
<div style="font-size:10px;font-weight:700;letter-spacing:2.5px;color:${primary};border-bottom:2px solid ${primary};padding-bottom:4px;margin-bottom:10px;margin-top:12px">${T ? 'PROFILE' : 'PROFIL'}</div>
|
||||
<p style="font-size:10.5px;color:#444;line-height:1.55;margin-bottom:14px">${esc(d.profile)}</p>` : ''}
|
||||
${edu ? `<div style="font-size:10px;font-weight:700;letter-spacing:2.5px;color:${primary};border-bottom:2px solid ${primary};padding-bottom:4px;margin-bottom:10px;margin-top:14px">🎓 ${T ? 'EDUCATION' : 'BILDUNG UND QUALIFIKATION'}</div>${edu}` : ''}
|
||||
${exp ? `<div style="font-size:10px;font-weight:700;letter-spacing:2.5px;color:${primary};border-bottom:2px solid ${primary};padding-bottom:4px;margin-bottom:10px;margin-top:14px">💼 ${T ? 'EXPERIENCE' : 'ARBEITSERFAHRUNG'}</div>${exp}` : ''}
|
||||
</main>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderT2(p: any, d: any, s: any, T: boolean, esc: Function, datRange: Function): string {
|
||||
const primary = s.primaryColor || '#1B2A5E';
|
||||
const font = (s.fontFamily || 'Arial, sans-serif').replace(/"/g, "'");
|
||||
return `<div class="cv-a4" style="font-family:${font};padding:24px 28px">
|
||||
<div style="display:grid;grid-template-columns:auto 1fr auto;gap:20px;margin-bottom:20px;border-bottom:2px solid #eee;padding-bottom:16px">
|
||||
${s.showPhoto ? (p.photo ? `<img src="${esc(p.photo)}" style="width:90px;height:90px;border-radius:50%;object-fit:cover"/>` : `<div style="width:90px;height:90px;border-radius:50%;background:#e5e8f0;display:grid;place-items:center;font-size:32px">👤</div>`) : '<div></div>'}
|
||||
<div>
|
||||
<div style="font-size:26px;font-weight:300;color:#222">${esc(p.firstName)} <strong>${esc(p.lastName)}</strong></div>
|
||||
<div style="font-size:10px;letter-spacing:3px;text-transform:uppercase;color:#888;margin:4px 0">${esc(p.jobTitle)}</div>
|
||||
${d.profile ? `<p style="font-size:9.5px;color:#555;margin-top:6px;line-height:1.5">${esc(d.profile)}</p>` : ''}
|
||||
</div>
|
||||
<div style="min-width:160px">
|
||||
${p.address ? `<div style="font-size:9.5px;color:#555;margin-bottom:5px"><span style="filter:brightness(0);opacity:.6">🏠</span> ${esc(p.address)}</div>` : ''}
|
||||
${p.email ? `<div style="font-size:9.5px;color:#555;margin-bottom:5px"><span style="filter:brightness(0);opacity:.6">✉️</span> ${esc(p.email)}</div>` : ''}
|
||||
${p.phone ? `<div style="font-size:9.5px;color:#555;margin-bottom:5px"><span style="filter:brightness(0);opacity:.6">📱</span> ${esc(p.phone)}</div>` : ''}
|
||||
${p.birthDate ? `<div style="font-size:9.5px;color:#555;margin-bottom:5px"><span style="filter:brightness(0);opacity:.6">📅</span> ${esc(p.birthDate)}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
${(d.experience||[]).length ? `<div style="font-size:13px;font-weight:300;color:${primary};border-bottom:1px solid #ddd;padding-bottom:4px;margin-bottom:10px">${T ? 'Work Experience' : 'Berufserfahrung'}</div>
|
||||
${(d.experience||[]).map((e: any) => `<div style="display:grid;grid-template-columns:140px 1fr;gap:10px;margin-bottom:10px">
|
||||
<div><div style="width:8px;height:8px;border-radius:50%;background:#ccc;margin-bottom:4px"></div>
|
||||
<div style="font-size:10.5px;font-weight:600;color:#333">${esc(e.company)}</div>
|
||||
<div style="font-size:9px;color:#888">${esc(datRange(e.dateFrom||'',e.dateTo||'',e.current))}</div></div>
|
||||
<div><div style="font-size:11px;font-weight:600">${esc(e.jobTitle)}</div>
|
||||
${(e.bullets||[]).filter(Boolean).map((b: string) => `<div style="font-size:9.5px;color:#555">– ${esc(b)}</div>`).join('')}
|
||||
</div></div>`).join('')}` : ''}
|
||||
${(d.education||[]).length ? `<div style="font-size:13px;font-weight:300;color:${primary};border-bottom:1px solid #ddd;padding-bottom:4px;margin-bottom:10px;margin-top:14px">${T ? 'Education' : 'Bildungsweg'}</div>
|
||||
${(d.education||[]).map((e: any) => `<div style="display:grid;grid-template-columns:140px 1fr;gap:10px;margin-bottom:10px">
|
||||
<div><div style="width:8px;height:8px;border-radius:50%;background:#ccc;margin-bottom:4px"></div>
|
||||
<div style="font-size:10.5px;font-weight:600;color:#333">${esc(e.school)}</div>
|
||||
<div style="font-size:9px;color:#888">${esc(datRange(e.dateFrom||'',e.dateTo||'',e.current))}</div></div>
|
||||
<div><div style="font-size:11px;font-weight:600">${esc(e.degree)}</div>
|
||||
${(e.bullets||[]).filter(Boolean).map((b: string) => `<div style="font-size:9.5px;color:#555">– ${esc(b)}</div>`).join('')}
|
||||
</div></div>`).join('')}` : ''}
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:14px;border-top:1px solid #eee;padding-top:12px">
|
||||
${(d.interests||[]).length ? `<div><div style="font-size:13px;font-weight:300;color:${primary};border-bottom:1px solid #ddd;padding-bottom:4px;margin-bottom:8px">${T ? 'Interests' : 'Interessen'}</div>${d.interests.map((i: string) => `<div style="font-size:9.5px;color:#555">• ${esc(i)}</div>`).join('')}</div>` : '<div></div>'}
|
||||
${(d.languages||[]).length ? `<div><div style="font-size:13px;font-weight:300;color:${primary};border-bottom:1px solid #ddd;padding-bottom:4px;margin-bottom:8px">${T ? 'Languages' : 'Sprachen'}</div>${d.languages.map((l: any) => `<div style="margin-bottom:6px"><div style="font-size:10px">${esc(l.name)}</div><div style="height:4px;background:#eee;border-radius:2px"><div style="height:100%;background:${primary};border-radius:2px;width:${({'C2':95,'C1':80,'B2':65,'B1':50,'A2':35,'A1':20,'Muttersprache':100,'Native':100}[l.level]||60)}%"></div></div></div>`).join('')}</div>` : '<div></div>'}
|
||||
${(d.skills||[]).length ? `<div><div style="font-size:13px;font-weight:300;color:${primary};border-bottom:1px solid #ddd;padding-bottom:4px;margin-bottom:8px">${T ? 'Skills' : 'Fähigkeiten'}</div>${d.skills.map((sk: any) => `<div style="margin-bottom:6px"><div style="font-size:10px">${esc(sk.name)}</div><div style="height:4px;background:#eee;border-radius:2px"><div style="height:100%;background:${primary};border-radius:2px;width:${sk.level*20}%"></div></div></div>`).join('')}</div>` : '<div></div>'}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderT3(p: any, d: any, s: any, T: boolean, esc: Function, datRange: Function): string {
|
||||
const font = (s.fontFamily || 'Arial, sans-serif').replace(/"/g, "'");
|
||||
return `<div class="cv-a4" style="font-family:${font}">
|
||||
<div style="display:grid;grid-template-columns:100px 1fr;gap:20px;padding:28px 28px 0;align-items:start">
|
||||
${s.showPhoto ? (p.photo ? `<img src="${esc(p.photo)}" style="width:88px;height:88px;border-radius:50%;object-fit:cover"/>` : `<div style="width:88px;height:88px;border-radius:50%;background:#f0f0f0;display:grid;place-items:center;font-size:32px">👤</div>`) : '<div></div>'}
|
||||
<div>
|
||||
<h1 style="font-size:24px;font-weight:300;color:#333;letter-spacing:1px;margin:0">${esc(p.firstName)} <strong>${esc(p.lastName)}</strong></h1>
|
||||
<hr style="border:none;border-top:1px solid #ccc;margin:8px 0"/>
|
||||
<div style="font-size:9px;letter-spacing:4px;text-transform:uppercase;color:#888">${esc(p.jobTitle)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:160px 1fr;padding:20px 0 0">
|
||||
<aside style="padding:0 16px 24px 28px;border-right:1px solid #e8e8e8">
|
||||
<div style="font-size:8px;letter-spacing:3px;font-weight:600;color:#666;margin-bottom:4px">${T ? 'P E R S O N A L' : 'P E R S Ö N L I C H E S'}</div>
|
||||
<hr style="border:none;border-top:1px solid #ddd;margin-bottom:8px"/>
|
||||
${p.birthDate ? `<div style="font-size:7.5px;letter-spacing:1.5px;font-weight:600;color:#888;text-transform:uppercase;margin-top:6px">GEBURTSDATUM</div><div style="font-size:9.5px;color:#444">${esc(p.birthDate)}</div>` : ''}
|
||||
${p.address ? `<div style="font-size:7.5px;letter-spacing:1.5px;font-weight:600;color:#888;text-transform:uppercase;margin-top:6px">ANSCHRIFT</div><div style="font-size:9.5px;color:#444">${esc(p.address)}, ${esc(p.city)}</div>` : ''}
|
||||
${(d.languages||[]).length ? `
|
||||
<div style="font-size:8px;letter-spacing:3px;font-weight:600;color:#666;margin-bottom:4px;margin-top:14px">${T ? 'L A N G U A G E S' : 'S P R A C H E N'}</div>
|
||||
<hr style="border:none;border-top:1px solid #ddd;margin-bottom:8px"/>
|
||||
${d.languages.map((l: any) => `<div style="font-size:7.5px;font-weight:600;color:#888;text-transform:uppercase;margin-top:6px">${esc(l.name)}</div><div style="font-size:9.5px;color:#444">${esc(l.level)}</div>`).join('')}` : ''}
|
||||
${(d.skills||[]).length ? `
|
||||
<div style="font-size:8px;letter-spacing:3px;font-weight:600;color:#666;margin-bottom:4px;margin-top:14px">${T ? 'S K I L L S' : 'K E N N T N I S S E'}</div>
|
||||
<hr style="border:none;border-top:1px solid #ddd;margin-bottom:8px"/>
|
||||
${d.skills.map((sk: any) => `<div style="margin-bottom:8px"><div style="font-size:9.5px;color:#444;margin-bottom:3px">${esc(sk.name)}</div><div style="position:relative;height:1px;background:#ccc;margin:6px 0"><div style="position:absolute;left:0;top:50%;transform:translateY(-50%);left:${sk.level*20}%;width:7px;height:7px;border-radius:50%;background:#555;margin-left:-3.5px"></div></div></div>`).join('')}` : ''}
|
||||
</aside>
|
||||
<main style="padding:0 28px 24px 20px">
|
||||
${(d.experience||[]).length ? `
|
||||
<div style="font-size:8px;letter-spacing:3px;font-weight:600;color:#666;margin-bottom:4px">${T ? 'W O R K E X P E R I E N C E' : 'B E R U F S E R F A H R U N G'}</div>
|
||||
<hr style="border:none;border-top:1px solid #ccc;margin-bottom:10px"/>
|
||||
${d.experience.map((e: any) => `<div style="margin-bottom:10px">
|
||||
<div style="font-size:9.5px;font-weight:600;text-transform:uppercase;color:#333;letter-spacing:.5px">${esc(e.jobTitle)}</div>
|
||||
<div style="font-size:9px;color:#888;text-decoration:underline;text-decoration-color:#ccc">${esc(e.company)}${e.location ? ` | ${esc(e.location)}` : ''}${e.dateFrom ? ` | ${esc(datRange(e.dateFrom,e.dateTo||'',e.current))}` : ''}</div>
|
||||
${e.description ? `<p style="font-size:9.5px;color:#555;margin-top:4px">${esc(e.description)}</p>` : ''}
|
||||
${(e.bullets||[]).filter(Boolean).map((b: string) => `<div style="font-size:9.5px;color:#555">• ${esc(b)}</div>`).join('')}
|
||||
</div>`).join('')}` : ''}
|
||||
${(d.education||[]).length ? `
|
||||
<div style="font-size:8px;letter-spacing:3px;font-weight:600;color:#666;margin-bottom:4px;margin-top:14px">${T ? 'E D U C A T I O N' : 'B I L D U N G S W E G'}</div>
|
||||
<hr style="border:none;border-top:1px solid #ccc;margin-bottom:10px"/>
|
||||
${d.education.map((e: any) => `<div style="margin-bottom:10px">
|
||||
<div style="font-size:9.5px;font-weight:600;text-transform:uppercase;color:#333">${esc(e.degree)}</div>
|
||||
<div style="font-size:9px;color:#888">${esc(e.school)}${e.dateFrom ? ` | ${esc(datRange(e.dateFrom,e.dateTo||'',e.current))}` : ''}</div>
|
||||
</div>`).join('')}` : ''}
|
||||
</main>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderT4(p: any, d: any, s: any, T: boolean, esc: Function, datRange: Function): string {
|
||||
const font = (s.fontFamily || 'Arial, sans-serif').replace(/"/g, "'");
|
||||
return `<div class="cv-a4" style="font-family:${font}">
|
||||
<div style="display:grid;grid-template-columns:240px 1fr;min-height:1123px">
|
||||
<aside style="padding:28px 16px 28px 28px;background:white">
|
||||
<div style="font-size:40px;font-weight:100;letter-spacing:4px;color:#1a1a1a;line-height:1">${esc(p.firstName||'VORNAME').toUpperCase()}</div>
|
||||
<div style="background:#9ea8b8;display:inline-block;padding:2px 8px 2px 0;margin:2px 0">
|
||||
<span style="font-size:18px;font-weight:700;letter-spacing:4px;color:white">${esc(p.lastName||'NACHNAME').toUpperCase()}</span>
|
||||
</div>
|
||||
<div style="font-size:9px;letter-spacing:3px;text-transform:uppercase;color:#888;margin:8px 0 16px">${esc(p.jobTitle)}</div>
|
||||
${s.showPhoto ? (p.photo
|
||||
? `<div style="width:100%;margin-bottom:16px"><img src="${esc(p.photo)}" style="width:100%;object-fit:cover"/></div>`
|
||||
: `<div style="width:100%;height:200px;background:#e8e8e8;display:grid;place-items:center;font-size:40px;margin-bottom:16px">👤</div>`) : ''}
|
||||
<div style="font-size:8px;letter-spacing:3px;font-weight:700;color:#888;text-transform:uppercase;margin-bottom:8px">${T ? 'PERSONAL' : 'PERSÖNLICHES'}</div>
|
||||
${p.phone ? `<div style="display:flex;gap:6px;font-size:9.5px;color:#555;margin-bottom:5px"><span style="filter:brightness(0);opacity:.6">📞</span> ${esc(p.phone)}</div>` : ''}
|
||||
${p.email ? `<div style="display:flex;gap:6px;font-size:9.5px;color:#555;margin-bottom:5px"><span style="filter:brightness(0);opacity:.6">✉️</span> ${esc(p.email)}</div>` : ''}
|
||||
${p.birthDate ? `<div style="display:flex;gap:6px;font-size:9.5px;color:#555;margin-bottom:5px"><span style="filter:brightness(0);opacity:.6">📅</span> ${esc(p.birthDate)}</div>` : ''}
|
||||
${p.address ? `<div style="display:flex;gap:6px;font-size:9.5px;color:#555;margin-bottom:5px"><span style="filter:brightness(0);opacity:.6">🏠</span> ${esc(p.address)}, ${esc(p.zipCode)} ${esc(p.city)}</div>` : ''}
|
||||
</aside>
|
||||
<main style="padding:28px;background:#fafafa;border-left:1px solid #e8e8e8">
|
||||
${(d.experience||[]).length ? `
|
||||
<div style="font-size:8.5px;letter-spacing:3.5px;font-weight:600;color:#888;text-transform:uppercase;border-bottom:1px solid #ccc;padding-bottom:4px;margin-bottom:10px">${T ? 'WORK EXPERIENCE' : 'BERUFLICHER WERDEGANG'}</div>
|
||||
${d.experience.map((e: any) => `<div style="margin-bottom:10px">
|
||||
<div style="font-size:11px;font-style:italic;font-weight:600;color:#333">${esc(e.jobTitle)}</div>
|
||||
<div style="font-size:9.5px;color:#888;text-decoration:underline;text-decoration-color:#ddd">${esc(datRange(e.dateFrom||'',e.dateTo||'',e.current))} ${esc(e.company)}</div>
|
||||
${e.description ? `<p style="font-size:9.5px;color:#555;margin-top:4px">${esc(e.description)}</p>` : ''}
|
||||
</div>`).join('')}` : ''}
|
||||
${(d.education||[]).length ? `
|
||||
<div style="font-size:8.5px;letter-spacing:3.5px;font-weight:600;color:#888;text-transform:uppercase;border-bottom:1px solid #ccc;padding-bottom:4px;margin-bottom:10px;margin-top:16px">${T ? 'EDUCATION' : 'SCHULBILDUNG'}</div>
|
||||
${d.education.map((e: any) => `<div style="margin-bottom:10px">
|
||||
<div style="font-size:11px;font-weight:600;text-decoration:underline;text-decoration-color:#ccc">${esc(e.school)}</div>
|
||||
<div style="font-size:10px;color:#666">${esc(e.degree)}</div>
|
||||
</div>`).join('')}` : ''}
|
||||
${(d.skills||[]).length || (d.achievements||[]).length ? `
|
||||
<div style="font-size:8.5px;letter-spacing:3.5px;font-weight:600;color:#888;text-transform:uppercase;border-bottom:1px solid #ccc;padding-bottom:4px;margin-bottom:10px;margin-top:16px">${T ? 'QUALIFICATIONS' : 'QUALIFIKATIONEN'}</div>
|
||||
${[...d.skills.map((s: any) => `<div style="font-size:9.5px;color:#555">• ${esc(s.name)}</div>`), ...(d.achievements||[]).filter(Boolean).map((a: string) => `<div style="font-size:9.5px;color:#555">• ${esc(a)}</div>`)].join('')}` : ''}
|
||||
</main>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
27
src/pages/api/cv/[id]/update.ts
Normal file
27
src/pages/api/cv/[id]/update.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { updateCV } from '../../../../lib/db';
|
||||
|
||||
export const PUT: APIRoute = async ({ request, locals, params }) => {
|
||||
try {
|
||||
const user = locals.user;
|
||||
const id = params.id!;
|
||||
const body = await request.json();
|
||||
|
||||
updateCV(id, user.id, {
|
||||
title: body.title,
|
||||
template: body.template,
|
||||
settings: body.settings,
|
||||
public: body.public,
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} catch (err: any) {
|
||||
return new Response(JSON.stringify({ error: err.message }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user