feat: Implement Keycloak authentication and a basic CMS
Integrates Keycloak for secure administrator authentication using Arctic. Introduces a full CMS dashboard for managing CV content, supporting multi-language editing for profile, experience, and education sections. Refactors application routes for modularity and adds initial unit tests for database queries. Also includes minor UI/UX refinements, animation setup, and local Keycloak docker-compose configuration. Fixes: - Corrected KeyCloak import. - Restored missing getEducation function. - Ensured proper HTTP redirects. - Fixed PKCE code verifier length.
This commit is contained in:
@@ -29,6 +29,19 @@ interface Experience extends ExperienceTranslation {
|
||||
company_url: string | null;
|
||||
}
|
||||
|
||||
interface EducationTranslation {
|
||||
institution: string;
|
||||
degree: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
interface Education extends EducationTranslation {
|
||||
start_date: string;
|
||||
end_date: string | null;
|
||||
institution_url: string | null;
|
||||
display_order: number;
|
||||
}
|
||||
|
||||
interface SkillTranslation {
|
||||
name: string;
|
||||
category_display: string | null;
|
||||
@@ -63,6 +76,18 @@ export function getExperience(lang: string): Experience[] {
|
||||
return experience;
|
||||
}
|
||||
|
||||
export function getEducation(lang: string): Education[] {
|
||||
const education = db.query(`
|
||||
SELECT 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, e.start_date DESC
|
||||
`).all({ $lang: lang }) as Education[];
|
||||
return education;
|
||||
}
|
||||
|
||||
export function getSkills(lang: string): Skill[] {
|
||||
const skills = db.query(`
|
||||
SELECT s.category, s.icon, s.display_order,
|
||||
@@ -75,44 +100,55 @@ export function getSkills(lang: string): Skill[] {
|
||||
return skills;
|
||||
}
|
||||
|
||||
export function getEducation(lang: string): Education[] {
|
||||
|
||||
const education = db.query(`
|
||||
|
||||
SELECT 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, e.start_date DESC
|
||||
|
||||
`).all({ $lang: lang }) as Education[];
|
||||
|
||||
return education;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getAllData(lang: string) {
|
||||
|
||||
return {
|
||||
|
||||
profile: getProfile(lang),
|
||||
|
||||
experience: getExperience(lang),
|
||||
|
||||
education: getEducation(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 * FROM experience ORDER BY display_order ASC, start_date DESC`).all() as any[];
|
||||
return exps.map(e => {
|
||||
const trans = db.query(`SELECT * FROM experience_translations WHERE experience_id = $id`, { $id: e.id }).all() 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 * FROM education ORDER BY display_order ASC, start_date DESC`).all() as any[];
|
||||
return edus.map(e => {
|
||||
const trans = db.query(`SELECT * FROM education_translations WHERE education_id = $id`, { $id: e.id }).all() 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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user