Introduces a custom migration system for SQLite, allowing incremental and safe schema evolution. Adds a new 'Projects' section to the CV, including database tables, public UI, and full management in the admin dashboard with live editing, drag-and-drop reordering, and collapsible forms. Updates: - and for schema management. - with script. - to use migrations. - to rely on migrations. - and for new project data operations. - and for Projects UI. - and to integrate the Projects section. Also updates: - to automatically import Keycloak realm on startup. - for the Elysia app build. - with refined print styles (omitting socials and about).
233 lines
7.4 KiB
TypeScript
233 lines
7.4 KiB
TypeScript
import { db } from "./schema";
|
|
|
|
interface ProfileTranslation {
|
|
name: string;
|
|
job_title: string;
|
|
summary: string | null;
|
|
location: string | null;
|
|
}
|
|
|
|
interface Profile extends ProfileTranslation {
|
|
email: string;
|
|
phone: string | null;
|
|
website: string | null;
|
|
github_url: string | null;
|
|
linkedin_url: string | null;
|
|
avatar_url: string | null;
|
|
}
|
|
|
|
interface ExperienceTranslation {
|
|
company_name: string;
|
|
role: string;
|
|
description: string | null;
|
|
location: string | null;
|
|
}
|
|
|
|
interface Experience extends ExperienceTranslation {
|
|
id: number; // Add ID for ordering
|
|
start_date: string;
|
|
end_date: string | null;
|
|
company_url: string | null;
|
|
display_order: number; // Re-added
|
|
}
|
|
|
|
interface EducationTranslation {
|
|
institution: string;
|
|
degree: string;
|
|
description: string | null;
|
|
}
|
|
|
|
interface Education extends EducationTranslation {
|
|
id: number; // Add ID for ordering
|
|
start_date: string;
|
|
end_date: string | null;
|
|
institution_url: string | null;
|
|
display_order: number; // Re-added
|
|
}
|
|
|
|
interface SkillTranslation {
|
|
name: string;
|
|
category_display: string | null;
|
|
}
|
|
|
|
interface Skill extends SkillTranslation {
|
|
category: string;
|
|
icon: string | null;
|
|
display_order: number;
|
|
}
|
|
|
|
interface ProjectTranslation {
|
|
name: string;
|
|
description: string | null;
|
|
}
|
|
|
|
interface Project extends ProjectTranslation {
|
|
id: number;
|
|
project_url: string | null;
|
|
image_url: string | null;
|
|
tech_stack: string | null;
|
|
display_order: number;
|
|
}
|
|
|
|
export function getProfile(lang: string): Profile | null {
|
|
const profile = db.query(`
|
|
SELECT p.email, p.phone, p.website, p.github_url, p.linkedin_url, p.avatar_url,
|
|
pt.name, pt.job_title, pt.summary, pt.location
|
|
FROM profile p
|
|
JOIN profile_translations pt ON p.id = pt.profile_id
|
|
WHERE pt.language_code = $lang
|
|
`).get({ $lang: lang }) as Profile | null;
|
|
return profile;
|
|
}
|
|
|
|
export function getExperience(lang: string): Experience[] {
|
|
const experience = db.query(`
|
|
SELECT e.id, e.start_date, e.end_date, e.company_url, e.display_order,
|
|
et.company_name, et.role, et.description, et.location
|
|
FROM experience e
|
|
JOIN experience_translations et ON e.id = et.experience_id
|
|
WHERE et.language_code = $lang
|
|
ORDER BY e.display_order ASC
|
|
`).all({ $lang: lang }) as Experience[];
|
|
return experience;
|
|
}
|
|
|
|
export function getEducation(lang: string): Education[] {
|
|
const education = db.query(`
|
|
SELECT e.id, e.start_date, e.end_date, e.institution_url, e.display_order,
|
|
et.institution, et.degree, et.description
|
|
FROM education e
|
|
JOIN education_translations et ON e.id = et.education_id
|
|
WHERE et.language_code = $lang
|
|
ORDER BY e.display_order ASC
|
|
`).all({ $lang: lang }) as Education[];
|
|
return education;
|
|
}
|
|
|
|
export function getProjects(lang: string): Project[] {
|
|
const projects = db.query(`
|
|
SELECT p.id, p.project_url, p.image_url, p.tech_stack, p.display_order,
|
|
pt.name, pt.description
|
|
FROM projects p
|
|
JOIN project_translations pt ON p.id = pt.project_id
|
|
WHERE pt.language_code = $lang
|
|
ORDER BY p.display_order ASC
|
|
`).all({ $lang: lang }) as Project[];
|
|
return projects;
|
|
}
|
|
|
|
export function getSkills(lang: string): Skill[] {
|
|
const skills = db.query(`
|
|
SELECT s.category, s.icon, s.display_order,
|
|
st.name, st.category_display
|
|
FROM skills s
|
|
JOIN skill_translations st ON s.id = st.skill_id
|
|
WHERE st.language_code = $lang
|
|
ORDER BY s.display_order ASC, s.category ASC, st.name ASC
|
|
`).all({ $lang: lang }) as Skill[];
|
|
return skills;
|
|
}
|
|
|
|
export function getAllData(lang: string) {
|
|
return {
|
|
profile: getProfile(lang),
|
|
experience: getExperience(lang),
|
|
education: getEducation(lang),
|
|
projects: getProjects(lang),
|
|
skills: getSkills(lang),
|
|
};
|
|
}
|
|
|
|
// --- Admin Queries ---
|
|
|
|
export function getAdminProfile() {
|
|
const profile = db.query(`SELECT * FROM profile WHERE id = 1`).get() as any;
|
|
const translations = db.query(`SELECT * FROM profile_translations WHERE profile_id = 1`).all() as any[];
|
|
|
|
translations.forEach(t => {
|
|
profile[`name_${t.language_code}`] = t.name;
|
|
profile[`job_title_${t.language_code}`] = t.job_title;
|
|
profile[`summary_${t.language_code}`] = t.summary;
|
|
profile[`location_${t.language_code}`] = t.location;
|
|
});
|
|
return profile;
|
|
}
|
|
|
|
export function getAdminExperience() {
|
|
const exps = db.query(`SELECT e.*, MAX(e.display_order) OVER () AS max_order FROM experience e ORDER BY e.display_order ASC`).all() as any[];
|
|
return exps.map(e => {
|
|
const trans = db.query(`SELECT * FROM experience_translations WHERE experience_id = $id`).all({ $id: e.id }) as any[];
|
|
trans.forEach(t => {
|
|
e[`company_name_${t.language_code}`] = t.company_name;
|
|
e[`role_${t.language_code}`] = t.role;
|
|
e[`description_${t.language_code}`] = t.description;
|
|
e[`location_${t.language_code}`] = t.location;
|
|
});
|
|
return e;
|
|
});
|
|
}
|
|
|
|
export function getAdminExperienceById(id: number) {
|
|
const e = db.query(`SELECT * FROM experience WHERE id = $id`).get({ $id: id }) as any;
|
|
if (!e) return null;
|
|
|
|
const trans = db.query(`SELECT * FROM experience_translations WHERE experience_id = $id`).all({ $id: id }) as any[];
|
|
trans.forEach(t => {
|
|
e[`company_name_${t.language_code}`] = t.company_name;
|
|
e[`role_${t.language_code}`] = t.role;
|
|
e[`description_${t.language_code}`] = t.description;
|
|
e[`location_${t.language_code}`] = t.location;
|
|
});
|
|
return e;
|
|
}
|
|
|
|
export function getAdminEducation() {
|
|
const edus = db.query(`SELECT e.*, MAX(e.display_order) OVER () AS max_order FROM education e ORDER BY e.display_order ASC`).all() as any[];
|
|
return edus.map(e => {
|
|
const trans = db.query(`SELECT * FROM education_translations WHERE education_id = $id`).all({ $id: e.id }) as any[];
|
|
trans.forEach(t => {
|
|
e[`institution_${t.language_code}`] = t.institution;
|
|
e[`degree_${t.language_code}`] = t.degree;
|
|
e[`description_${t.language_code}`] = t.description;
|
|
});
|
|
return e;
|
|
});
|
|
}
|
|
|
|
export function getAdminEducationById(id: number) {
|
|
const e = db.query(`SELECT * FROM education WHERE id = $id`).get({ $id: id }) as any;
|
|
if (!e) return null;
|
|
|
|
const trans = db.query(`SELECT * FROM education_translations WHERE education_id = $id`).all({ $id: id }) as any[];
|
|
trans.forEach(t => {
|
|
e[`institution_${t.language_code}`] = t.institution;
|
|
e[`degree_${t.language_code}`] = t.degree;
|
|
e[`description_${t.language_code}`] = t.description;
|
|
});
|
|
return e;
|
|
}
|
|
|
|
export function getAdminProjects() {
|
|
const projs = db.query(`SELECT p.*, MAX(p.display_order) OVER () AS max_order FROM projects p ORDER BY p.display_order ASC`).all() as any[];
|
|
return projs.map(p => {
|
|
const trans = db.query(`SELECT * FROM project_translations WHERE project_id = $id`).all({ $id: p.id }) as any[];
|
|
trans.forEach(t => {
|
|
p[`name_${t.language_code}`] = t.name;
|
|
p[`description_${t.language_code}`] = t.description;
|
|
});
|
|
return p;
|
|
});
|
|
}
|
|
|
|
export function getAdminProjectById(id: number) {
|
|
const p = db.query(`SELECT * FROM projects WHERE id = $id`).get({ $id: id }) as any;
|
|
if (!p) return null;
|
|
|
|
const trans = db.query(`SELECT * FROM project_translations WHERE project_id = $id`).all({ $id: id }) as any[];
|
|
trans.forEach(t => {
|
|
p[`name_${t.language_code}`] = t.name;
|
|
p[`description_${t.language_code}`] = t.description;
|
|
});
|
|
return p;
|
|
}
|