+
+ {/* Grouping by category could be done here if the data supports it nicely,
+ but for now we'll do a clean tag cloud with categories visually distinct if needed */}
+
+
+
+);
diff --git a/src/db/queries.ts b/src/db/queries.ts
new file mode 100644
index 0000000..7544a56
--- /dev/null
+++ b/src/db/queries.ts
@@ -0,0 +1,118 @@
+import { db } from "./schema";
+
+interface ProfileTranslation {
+ name: string;
+ job_title: string;
+ summary: string | null;
+ location: string | null;
+}
+
+interface Profile extends ProfileTranslation {
+ email: string;
+ phone: string | null;
+ website: string | null;
+ github_url: string | null;
+ linkedin_url: string | null;
+ avatar_url: string | null;
+}
+
+interface ExperienceTranslation {
+ company_name: string;
+ role: string;
+ description: string | null;
+ location: string | null;
+}
+
+interface Experience extends ExperienceTranslation {
+ start_date: string;
+ end_date: string | null;
+ company_url: string | null;
+}
+
+interface SkillTranslation {
+ name: string;
+ category_display: string | null;
+}
+
+interface Skill extends SkillTranslation {
+ category: string;
+ icon: string | null;
+ display_order: number;
+}
+
+export function getProfile(lang: string): Profile | null {
+ const profile = db.query(`
+ SELECT p.email, p.phone, p.website, p.github_url, p.linkedin_url, p.avatar_url,
+ pt.name, pt.job_title, pt.summary, pt.location
+ FROM profile p
+ JOIN profile_translations pt ON p.id = pt.profile_id
+ WHERE pt.language_code = $lang
+ `).get({ $lang: lang }) as Profile | null;
+ return profile;
+}
+
+export function getExperience(lang: string): Experience[] {
+ const experience = db.query(`
+ SELECT e.start_date, e.end_date, e.company_url, e.display_order,
+ et.company_name, et.role, et.description, et.location
+ FROM experience e
+ JOIN experience_translations et ON e.id = et.experience_id
+ WHERE et.language_code = $lang
+ ORDER BY e.display_order ASC, e.start_date DESC
+ `).all({ $lang: lang }) as Experience[];
+ return experience;
+}
+
+export function getSkills(lang: string): Skill[] {
+ const skills = db.query(`
+ SELECT s.category, s.icon, s.display_order,
+ st.name, st.category_display
+ FROM skills s
+ JOIN skill_translations st ON s.id = st.skill_id
+ WHERE st.language_code = $lang
+ ORDER BY s.display_order ASC, s.category ASC, st.name ASC
+ `).all({ $lang: lang }) as 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),
+
+ };
+
+}
+
+
diff --git a/src/db/schema.ts b/src/db/schema.ts
new file mode 100644
index 0000000..17ab315
--- /dev/null
+++ b/src/db/schema.ts
@@ -0,0 +1,121 @@
+import { Database } from "bun:sqlite";
+
+const db = new Database("cv.sqlite", { create: true });
+
+// Enable foreign keys
+db.run("PRAGMA foreign_keys = ON;");
+
+export function initDB() {
+ // 1. Languages Table (to ensure referential integrity)
+ db.run(`
+ CREATE TABLE IF NOT EXISTS languages (
+ code TEXT PRIMARY KEY
+ );
+ `);
+
+ // 2. Profile (Singleton - structural info)
+ db.run(`
+ CREATE TABLE IF NOT EXISTS profile (
+ id INTEGER PRIMARY KEY CHECK (id = 1), -- Ensure only one profile exists
+ email TEXT NOT NULL,
+ phone TEXT,
+ website TEXT,
+ github_url TEXT,
+ linkedin_url TEXT,
+ avatar_url TEXT
+ );
+ `);
+
+ // 3. Profile Translations
+ db.run(`
+ CREATE TABLE IF NOT EXISTS profile_translations (
+ profile_id INTEGER NOT NULL,
+ language_code TEXT NOT NULL,
+ name TEXT NOT NULL,
+ job_title TEXT NOT NULL,
+ summary TEXT,
+ location TEXT,
+ PRIMARY KEY (profile_id, language_code),
+ FOREIGN KEY (profile_id) REFERENCES profile(id) ON DELETE CASCADE,
+ FOREIGN KEY (language_code) REFERENCES languages(code)
+ );
+ `);
+
+ // 4. Experience (Structural)
+ db.run(`
+ CREATE TABLE IF NOT EXISTS experience (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ start_date TEXT NOT NULL, -- ISO8601 YYYY-MM
+ end_date TEXT, -- NULL = Current
+ company_url TEXT,
+ display_order INTEGER DEFAULT 0
+ );
+ `);
+
+ // 5. Experience Translations
+ db.run(`
+ CREATE TABLE IF NOT EXISTS experience_translations (
+ experience_id INTEGER NOT NULL,
+ language_code TEXT NOT NULL,
+ company_name TEXT NOT NULL,
+ role TEXT NOT NULL,
+ description TEXT, -- Supports Markdown/HTML
+ location TEXT,
+ PRIMARY KEY (experience_id, language_code),
+ FOREIGN KEY (experience_id) REFERENCES experience(id) ON DELETE CASCADE,
+ FOREIGN KEY (language_code) REFERENCES languages(code)
+ );
+ `);
+
+ // 6. Education (Structural)
+ db.run(`
+ CREATE TABLE IF NOT EXISTS education (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ start_date TEXT NOT NULL,
+ end_date TEXT,
+ institution_url TEXT,
+ display_order INTEGER DEFAULT 0
+ );
+ `);
+
+ // 7. Education Translations
+ db.run(`
+ CREATE TABLE IF NOT EXISTS education_translations (
+ education_id INTEGER NOT NULL,
+ language_code TEXT NOT NULL,
+ institution TEXT NOT NULL,
+ degree TEXT NOT NULL,
+ description TEXT,
+ PRIMARY KEY (education_id, language_code),
+ FOREIGN KEY (education_id) REFERENCES education(id) ON DELETE CASCADE,
+ FOREIGN KEY (language_code) REFERENCES languages(code)
+ );
+ `);
+
+ // 8. Skills (Categories & Items)
+ // Simply storing skills as items with a category.
+ db.run(`
+ CREATE TABLE IF NOT EXISTS skills (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ category TEXT NOT NULL, -- e.g., "frontend", "backend", "tools" (Internal key)
+ icon TEXT, -- class name for an icon library
+ display_order INTEGER DEFAULT 0
+ );
+ `);
+
+ db.run(`
+ CREATE TABLE IF NOT EXISTS skill_translations (
+ skill_id INTEGER NOT NULL,
+ language_code TEXT NOT NULL,
+ name TEXT NOT NULL, -- The display name
+ category_display TEXT, -- "Frontend Development" vs "Frontend Entwicklung"
+ PRIMARY KEY (skill_id, language_code),
+ FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE,
+ FOREIGN KEY (language_code) REFERENCES languages(code)
+ );
+ `);
+
+ console.log("Database schema initialized.");
+}
+
+export { db };
diff --git a/src/db/seed.ts b/src/db/seed.ts
new file mode 100644
index 0000000..fa26e3a
--- /dev/null
+++ b/src/db/seed.ts
@@ -0,0 +1,86 @@
+import { db, initDB } from "./schema";
+
+export function seedDB() {
+ // Initialize tables first
+ initDB();
+
+ // Check if data exists to avoid duplicates
+ const check = db.query("SELECT count(*) as count FROM languages").get() as { count: number };
+ if (check.count > 0) {
+ console.log("Database already seeded.");
+ return;
+ }
+
+ console.log("Seeding database...");
+
+ // 1. Languages
+ const insertLang = db.prepare("INSERT INTO languages (code) VALUES ($code)");
+ insertLang.run({ $code: "en" });
+ insertLang.run({ $code: "de" });
+
+ // 2. Profile
+ db.run(`
+ INSERT INTO profile (id, email, phone, website, github_url, linkedin_url, avatar_url)
+ VALUES (1, 'contact@johndoe.dev', '+49 123 456789', 'https://johndoe.dev', 'https://github.com/johndoe', 'https://linkedin.com/in/johndoe', 'https://placehold.co/400')
+ `);
+
+ const insertProfileTrans = db.prepare(`
+ INSERT INTO profile_translations (profile_id, language_code, name, job_title, summary, location)
+ VALUES ($pid, $code, $name, $title, $summary, $loc)
+ `);
+
+ insertProfileTrans.run({
+ $pid: 1, $code: "en", $name: "John Doe", $title: "Full Stack Developer",
+ $summary: "Passionate developer building minimalist and high-performance web applications.",
+ $loc: "Berlin, Germany"
+ });
+ insertProfileTrans.run({
+ $pid: 1, $code: "de", $name: "John Doe", $title: "Full Stack Entwickler",
+ $summary: "Leidenschaftlicher Entwickler für minimalistische und hochperformante Webanwendungen.",
+ $loc: "Berlin, Deutschland"
+ });
+
+ // 3. Experience
+ const insertExp = db.prepare(`
+ INSERT INTO experience (start_date, end_date, company_url, display_order)
+ VALUES ($start, $end, $url, $order)
+ RETURNING id
+ `);
+
+ // Job 1
+ const job1 = insertExp.get({ $start: "2023-01", $end: null, $url: "https://techcorp.com", $order: 1 }) as { id: number };
+
+ const insertExpTrans = db.prepare(`
+ INSERT INTO experience_translations (experience_id, language_code, company_name, role, description)
+ VALUES ($eid, $code, $comp, $role, $desc)
+ `);
+
+ insertExpTrans.run({
+ $eid: job1.id, $code: "en", $comp: "Tech Corp", $role: "Senior Engineer",
+ $desc: "Leading the frontend team and migrating legacy codebase to ElysiaJS."
+ });
+ insertExpTrans.run({
+ $eid: job1.id, $code: "de", $comp: "Tech Corp", $role: "Senior Entwickler",
+ $desc: "Leitung des Frontend-Teams und Migration der Legacy-Codebasis zu ElysiaJS."
+ });
+
+ // 4. Skills
+ const insertSkill = db.prepare(`
+ INSERT INTO skills (category, icon, display_order) VALUES ($cat, $icon, $order) RETURNING id
+ `);
+ const insertSkillTrans = db.prepare(`
+ INSERT INTO skill_translations (skill_id, language_code, name, category_display)
+ VALUES ($sid, $code, $name, $catDisplay)
+ `);
+
+ const s1 = insertSkill.get({ $cat: "tech", $icon: "code", $order: 1 }) as { id: number };
+ insertSkillTrans.run({ $sid: s1.id, $code: "en", $name: "TypeScript", $catDisplay: "Technologies" });
+ insertSkillTrans.run({ $sid: s1.id, $code: "de", $name: "TypeScript", $catDisplay: "Technologien" });
+
+ console.log("Seeding complete.");
+}
+
+// Allow running directly: bun src/db/seed.ts
+if (import.meta.main) {
+ seedDB();
+}
diff --git a/src/index.ts b/src/index.ts
deleted file mode 100644
index 9c1f7a1..0000000
--- a/src/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Elysia } from "elysia";
-
-const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
-
-console.log(
- `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
-);
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000..c0d64fc
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,51 @@
+import { Elysia, NotFoundError } from "elysia";
+import { html } from "@elysiajs/html";
+import * as elements from "typed-html";
+import { BaseHtml } from "./components/BaseHtml";
+import { Layout } from "./components/Layout";
+import { HeroSection, AboutSection, ExperienceSection, EducationSection, SkillsSection } from "./components/Sections";
+import { getAllData } from "./db/queries";
+
+const app = new Elysia()
+ .use(html())
+ .get("/", () => {
+ return Response.redirect("/en"); // Default to English
+ })
+ .get("/:lang", ({ params, html, set }) => {
+ const lang = params.lang as "en" | "de";
+ if (!["en", "de"].includes(lang)) {
+ throw new NotFoundError();
+ }
+
+ const data = getAllData(lang);
+ if (!data.profile) {
+ throw new NotFoundError("Profile data not found for selected language.");
+ }
+
+ return html(
+
+
+
+
+ {/* Separate About Section using the summary */}
+ {data.profile.summary && }
+
+
+
+
+ {/* Only render Education section if data exists */}
+ {data.education && data.education.length > 0 && (
+
+ )}
+
+
+
+
+
+ );
+ })
+ .listen(3000);
+
+console.log(
+ `Elysia is running at http://${app.server?.hostname}:${app.server?.port}`
+);
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..6a4a71f
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,11 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./src/**/*.{ts,tsx,html}",
+ ],
+ darkMode: 'class', // Enables dark mode based on 'dark' class in HTML
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 1ca2350..7542f04 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,103 +1,19 @@
{
"compilerOptions": {
- /* Visit https://aka.ms/tsconfig to read more about this file */
-
- /* Projects */
- // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
- // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
- // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
- // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
- // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
-
- /* Language and Environment */
- "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
- // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
- // "jsx": "preserve", /* Specify what JSX code is generated. */
- // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
- // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
- // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
- // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
- // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
- // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
- // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
- // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
- // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
-
- /* Modules */
- "module": "ES2022", /* Specify what module code is generated. */
- // "rootDir": "./", /* Specify the root folder within your source files. */
- "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
- // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
- // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
- // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
- // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
- "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */
- // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
- // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
- // "resolveJsonModule": true, /* Enable importing .json files. */
- // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
-
- /* JavaScript Support */
- // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
- // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
- // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
-
- /* Emit */
- // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
- // "declarationMap": true, /* Create sourcemaps for d.ts files. */
- // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
- // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
- // "outDir": "./", /* Specify an output folder for all emitted files. */
- // "removeComments": true, /* Disable emitting comments. */
- // "noEmit": true, /* Disable emitting files from a compilation. */
- // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
- // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
- // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
- // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
- // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
- // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
- // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
- // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
- // "newLine": "crlf", /* Set the newline character for emitting files. */
- // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
- // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
- // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
- // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
- // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
- // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
-
- /* Interop Constraints */
- // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
- // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
- // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
-
- /* Type Checking */
- "strict": true, /* Enable all strict type-checking options. */
- // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
- // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
- // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
- // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
- // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
- // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
- // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
- // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
- // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
- // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
- // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
- // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
- // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
- // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
- // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
- // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
- // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
- // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
-
- /* Completeness */
- // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ "lib": ["ESNext", "DOM"],
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleDetection": "force",
+ "jsx": "react",
+ "jsxFactory": "elements.createElement",
+ "jsxFragmentFactory": "elements.Fragment",
+ "allowJs": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true
}
-}
+}
\ No newline at end of file