import { getPool } from "./database"; import { SkillCategory, Skill } from "@/lib/types"; export class SkillsService { /** * Get all skill categories with their skills */ static async getSkillCategories(): Promise { const pool = getPool(); const query = ` SELECT sc.id as category_id, sc.name as category_name, sc.icon as category_icon, s.id as skill_id, s.name as skill_name, s.description as skill_description, s.icon as skill_icon, COALESCE( json_agg( sl.url ORDER BY sl.id ) FILTER (WHERE sl.url IS NOT NULL), '[]'::json ) as skill_links FROM skill_categories sc LEFT JOIN skills s ON sc.id = s.category_id LEFT JOIN skill_links sl ON s.id = sl.skill_id GROUP BY sc.id, sc.name, sc.icon, s.id, s.name, s.description, s.icon ORDER BY sc.name, s.name `; try { const result = await pool.query(query); // Group by category const categoriesMap = new Map(); for (const row of result.rows) { const categoryId = row.category_id; if (!categoriesMap.has(categoryId)) { categoriesMap.set(categoryId, { category: row.category_name, icon: row.category_icon, skills: [], }); } if (row.skill_id) { const category = categoriesMap.get(categoryId)!; category.skills.push({ id: row.skill_id, name: row.skill_name, description: row.skill_description, icon: row.skill_icon, links: row.skill_links, }); } } return Array.from(categoriesMap.values()); } catch (error) { console.error("Error fetching skill categories:", error); throw new Error("Failed to fetch skill categories"); } } /** * Get skills by category */ static async getSkillsByCategory(categoryId: string): Promise { const pool = getPool(); const query = ` SELECT s.id, s.name, s.description, s.icon, COALESCE( json_agg(sl.url ORDER BY sl.id) FILTER (WHERE sl.url IS NOT NULL), '[]'::json ) as links FROM skills s LEFT JOIN skill_links sl ON s.id = sl.skill_id WHERE s.category_id = $1 GROUP BY s.id, s.name, s.description, s.icon ORDER BY s.name `; try { const result = await pool.query(query, [categoryId]); return result.rows.map((row) => ({ id: row.id, name: row.name, description: row.description, icon: row.icon, links: row.links, })); } catch (error) { console.error("Error fetching skills by category:", error); throw new Error("Failed to fetch skills by category"); } } /** * Create a new skill category */ static async createSkillCategory(category: { id: string; name: string; icon: string; }): Promise { const pool = getPool(); const query = ` INSERT INTO skill_categories (id, name, icon) VALUES ($1, $2, $3) `; try { await pool.query(query, [category.id, category.name, category.icon]); } catch (error) { console.error("Error creating skill category:", error); throw new Error("Failed to create skill category"); } } /** * Create a new skill */ static async createSkill(skill: { id: string; name: string; description: string; icon?: string; categoryId: string; links: string[]; }): Promise { const pool = getPool(); const client = await pool.connect(); try { await client.query("BEGIN"); // Insert skill const skillQuery = ` INSERT INTO skills (id, name, description, icon, category_id) VALUES ($1, $2, $3, $4, $5) `; await client.query(skillQuery, [ skill.id, skill.name, skill.description, skill.icon, skill.categoryId, ]); // Insert links if (skill.links.length > 0) { const linkQuery = ` INSERT INTO skill_links (skill_id, url) VALUES ($1, $2) `; for (const link of skill.links) { await client.query(linkQuery, [skill.id, link]); } } await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK"); console.error("Error creating skill:", error); throw new Error("Failed to create skill"); } finally { client.release(); } } /** * Bulk insert skills from JSON data */ static async bulkInsertSkillsFromJSON( skillCategoriesData: SkillCategory[] ): Promise { const pool = getPool(); const client = await pool.connect(); try { await client.query("BEGIN"); for (const categoryData of skillCategoriesData) { const categoryId = categoryData.category.toLowerCase(); // Insert category const categoryQuery = ` INSERT INTO skill_categories (id, name, icon) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, icon = EXCLUDED.icon `; await client.query(categoryQuery, [ categoryId, categoryData.category, categoryData.icon, ]); // Insert skills for (const skill of categoryData.skills) { const skillQuery = ` INSERT INTO skills (id, name, description, icon, category_id) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, icon = EXCLUDED.icon, category_id = EXCLUDED.category_id `; await client.query(skillQuery, [ skill.id, skill.name, skill.description, skill.icon, categoryId, ]); // Delete existing links await client.query("DELETE FROM skill_links WHERE skill_id = $1", [ skill.id, ]); // Insert new links if (skill.links && skill.links.length > 0) { const linkQuery = ` INSERT INTO skill_links (skill_id, url) VALUES ($1, $2) `; for (const link of skill.links) { await client.query(linkQuery, [skill.id, link]); } } } } await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK"); console.error("Error bulk inserting skills:", error); throw new Error("Failed to bulk insert skills"); } finally { client.release(); } } }