feat: refactor skills API and database schema

- Replaced file-based skill category loading with API calls in the GET and POST methods of the skills route.
- Added new `SkillsService` for handling skill category operations.
- Updated SQL initialization script to create `skill_categories`, `skills`, and `skill_links` tables with appropriate relationships.
- Enhanced `ApiClient` with methods for loading skill categories and creating new skills, improving API interaction.
- Introduced error handling for skill category creation and loading processes.
This commit is contained in:
Julien Froidefond
2025-08-21 09:55:35 +02:00
parent 345ff5baa0
commit 72b653de19
10 changed files with 715 additions and 28 deletions

94
lib/skill-file-loader.ts Normal file
View File

@@ -0,0 +1,94 @@
import fs from "fs";
import path from "path";
import { SkillCategory } from "./types";
/**
* Load all skill categories from JSON files in the data/skills directory
*/
export function loadSkillCategoriesFromFiles(): SkillCategory[] {
const dataDir = path.join(process.cwd(), "data", "skills");
const skillCategories: SkillCategory[] = [];
try {
// Read all JSON files in the skills directory
const files = fs
.readdirSync(dataDir)
.filter((file) => file.endsWith(".json"));
console.log(`📁 Found ${files.length} JSON files: ${files.join(", ")}`);
// Load all JSON files
for (const file of files) {
const filePath = path.join(dataDir, file);
const categoryName = path.basename(file, ".json");
try {
console.log(`📖 Loading ${file}...`);
const fileContent = fs.readFileSync(filePath, "utf-8");
const data = JSON.parse(fileContent);
// Validate that it's a proper SkillCategory
if (data.category && data.icon && Array.isArray(data.skills)) {
skillCategories.push(data);
} else {
console.warn(`⚠️ Invalid skill category format in ${file}`);
}
} catch (error) {
console.error(`❌ Error loading ${file}:`, error);
// Continue with other files instead of failing completely
}
}
console.log(`📊 Successfully loaded ${skillCategories.length} categories`);
return skillCategories;
} catch (error) {
console.error("❌ Error reading skills directory:", error);
return [];
}
}
/**
* Get the list of available skill category files
*/
export function getSkillCategoryFiles(): string[] {
try {
const dataDir = path.join(process.cwd(), "data", "skills");
return fs
.readdirSync(dataDir)
.filter((file) => file.endsWith(".json"))
.map((file) => path.basename(file, ".json"));
} catch (error) {
console.error("❌ Error reading skills directory:", error);
return [];
}
}
/**
* Load a specific skill category by filename
*/
export function loadSkillCategoryFromFile(
categoryName: string
): SkillCategory | null {
try {
const dataDir = path.join(process.cwd(), "data", "skills");
const filePath = path.join(dataDir, `${categoryName}.json`);
if (!fs.existsSync(filePath)) {
console.warn(`⚠️ Skill category file not found: ${categoryName}.json`);
return null;
}
const fileContent = fs.readFileSync(filePath, "utf-8");
const data = JSON.parse(fileContent);
// Validate that it's a proper SkillCategory
if (data.category && data.icon && Array.isArray(data.skills)) {
return data;
} else {
console.warn(`⚠️ Invalid skill category format in ${categoryName}.json`);
return null;
}
} catch (error) {
console.error(`❌ Error loading skill category ${categoryName}:`, error);
return null;
}
}