mirror of
https://github.com/prdlk/cv.git
synced 2026-08-02 17:31:41 +00:00
init(app): Setup client package for web api specific logic
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/** 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user