Files
cv/app/worker/db.ts
T

162 lines
4.8 KiB
TypeScript
Raw Normal View History

/** 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,
};
}