mirror of
https://github.com/prdlk/cv.git
synced 2026-08-02 17:31:41 +00:00
162 lines
4.8 KiB
TypeScript
162 lines
4.8 KiB
TypeScript
/** Row shapes and corpus loader for the cv-tailor D1 database. */
|
||||
|
|
|
|||
|
|
export interface ProfileRow {
|
|||
|
|
full_name: string;
|
|||
|
|
email: string | null;
|
|||
|
|
phone: string | null;
|
|||
|
|
website: string | null;
|
|||
|
|
linkedin: string | null;
|
|||
|
|
github: string | null;
|
|||
|
|
twitter: string | null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface SummaryRow {
|
|||
|
|
role: string;
|
|||
|
|
summary: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface ExperienceItem {
|
|||
|
|
slug: string;
|
|||
|
|
title: string;
|
|||
|
|
company: string;
|
|||
|
|
start_date: string;
|
|||
|
|
end_date: string | null;
|
|||
|
|
location: string | null;
|
|||
|
|
profile_heading: string | null;
|
|||
|
|
bullets: string[];
|
|||
|
|
skills: string[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface ProjectItem {
|
|||
|
|
slug: string;
|
|||
|
|
title: string;
|
|||
|
|
start_date: string | null;
|
|||
|
|
end_date: string | null;
|
|||
|
|
url: string | null;
|
|||
|
|
associated_with: string | null;
|
|||
|
|
bullets: string[];
|
|||
|
|
skills: string[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface SkillRow {
|
|||
|
|
name: string;
|
|||
|
|
category: "language" | "framework" | "methodology";
|
|||
|
|
grp: string | null;
|
|||
|
|
tier: string | null;
|
|||
|
|
position: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface EducationRow {
|
|||
|
|
school: string;
|
|||
|
|
degree: string | null;
|
|||
|
|
field: string | null;
|
|||
|
|
start_date: string | null;
|
|||
|
|
end_date: string | null;
|
|||
|
|
activities: string | null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface HonorRow {
|
|||
|
|
title: string;
|
|||
|
|
date: string | null;
|
|||
|
|
institution: string | null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface OrganizationRow {
|
|||
|
|
name: string;
|
|||
|
|
url: string | null;
|
|||
|
|
title: string | null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface Corpus {
|
|||
|
|
profile: ProfileRow;
|
|||
|
|
summaries: SummaryRow[];
|
|||
|
|
experience: ExperienceItem[];
|
|||
|
|
projects: ProjectItem[];
|
|||
|
|
skills: SkillRow[];
|
|||
|
|
education: EducationRow | null;
|
|||
|
|
honors: HonorRow[];
|
|||
|
|
organizations: OrganizationRow[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface BulletRow {
|
|||
|
|
parent_type: string;
|
|||
|
|
parent_slug: string;
|
|||
|
|
position: number;
|
|||
|
|
text: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface ItemSkillRow {
|
|||
|
|
parent_type: string;
|
|||
|
|
parent_slug: string;
|
|||
|
|
skill: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type ExperienceDbRow = Omit<ExperienceItem, "bullets" | "skills">;
|
|||
|
|
type ProjectDbRow = Omit<ProjectItem, "bullets" | "skills">;
|
|||
|
|
|
|||
|
|
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* "2021-03-01".."" -> "Mar 2021 – Present" (month style) or "2021 – Present" (year style).
|
|||
|
|
* Uses an en dash; the LaTeX escaper converts it to `--`.
|
|||
|
|
*/
|
|||
|
|
export function formatRange(
|
|||
|
|
start: string | null,
|
|||
|
|
end: string | null,
|
|||
|
|
style: "month" | "year",
|
|||
|
|
): string {
|
|||
|
|
if (!start) return "";
|
|||
|
|
let from: string;
|
|||
|
|
let to = "Present";
|
|||
|
|
if (style === "month") {
|
|||
|
|
from = `${MONTHS[Number(start.slice(5, 7)) - 1]} ${start.slice(0, 4)}`;
|
|||
|
|
if (end) to = `${MONTHS[Number(end.slice(5, 7)) - 1]} ${end.slice(0, 4)}`;
|
|||
|
|
} else {
|
|||
|
|
from = start.slice(0, 4);
|
|||
|
|
if (end) to = end.slice(0, 4);
|
|||
|
|
}
|
|||
|
|
return `${from} – ${to}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function loadCorpus(db: D1Database): Promise<Corpus> {
|
|||
|
|
const [profile, summaries, experience, projects, bullets, itemSkills, skills, education, honors, organizations] =
|
|||
|
|
await Promise.all([
|
|||
|
|
db.prepare("SELECT * FROM profile LIMIT 1").all<ProfileRow>(),
|
|||
|
|
db.prepare("SELECT role, summary FROM summaries").all<SummaryRow>(),
|
|||
|
|
db.prepare("SELECT * FROM experience ORDER BY start_date DESC").all<ExperienceDbRow>(),
|
|||
|
|
db.prepare("SELECT * FROM projects ORDER BY start_date DESC").all<ProjectDbRow>(),
|
|||
|
|
db
|
|||
|
|
.prepare("SELECT parent_type, parent_slug, position, text FROM bullets ORDER BY position")
|
|||
|
|
.all<BulletRow>(),
|
|||
|
|
db.prepare("SELECT parent_type, parent_slug, skill FROM item_skills").all<ItemSkillRow>(),
|
|||
|
|
db
|
|||
|
|
.prepare("SELECT name, category, grp, tier, position FROM skills ORDER BY position")
|
|||
|
|
.all<SkillRow>(),
|
|||
|
|
db.prepare("SELECT * FROM education LIMIT 1").all<EducationRow>(),
|
|||
|
|
db.prepare("SELECT title, date, institution FROM honors").all<HonorRow>(),
|
|||
|
|
db.prepare("SELECT name, url, title FROM organizations").all<OrganizationRow>(),
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const profileRow = profile.results[0];
|
|||
|
|
if (!profileRow) throw new Error("D1 is empty — run `bun run sync` first.");
|
|||
|
|
|
|||
|
|
const experienceItems: ExperienceItem[] = experience.results.map((r) => ({ ...r, bullets: [], skills: [] }));
|
|||
|
|
const projectItems: ProjectItem[] = projects.results.map((r) => ({ ...r, bullets: [], skills: [] }));
|
|||
|
|
const bySlug = new Map<string, { bullets: string[]; skills: string[] }>();
|
|||
|
|
for (const item of experienceItems) bySlug.set(`experience:${item.slug}`, item);
|
|||
|
|
for (const item of projectItems) bySlug.set(`project:${item.slug}`, item);
|
|||
|
|
for (const b of bullets.results) bySlug.get(`${b.parent_type}:${b.parent_slug}`)?.bullets.push(b.text);
|
|||
|
|
for (const s of itemSkills.results) bySlug.get(`${s.parent_type}:${s.parent_slug}`)?.skills.push(s.skill);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
profile: profileRow,
|
|||
|
|
summaries: summaries.results,
|
|||
|
|
experience: experienceItems,
|
|||
|
|
projects: projectItems,
|
|||
|
|
skills: skills.results,
|
|||
|
|
education: education.results[0] ?? null,
|
|||
|
|
honors: honors.results,
|
|||
|
|
organizations: organizations.results,
|
|||
|
|
};
|
|||
|
|
}
|