Files
elysiacv/src/db/queries.ts
Tuan-Dat Tran 3de8f6a971 feat(db): Implement schema migrations and add Projects section
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).
2025-11-22 11:20:03 +01:00

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;
}