Files
cv/app/worker/db.ts
T

162 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 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,
};
}