// Seed controlled vocabulary (categories/subcategories) and skills // from shared/skills.js (SKILL_HIERARCHY) into the local SQLite DB. // // Usage: // cd backend // node scripts/seed-skills-from-shared.js // // Optionally set DATABASE_PATH to seed a custom DB file. const path = require('path') const fs = require('fs') const crypto = require('crypto') const Database = require('better-sqlite3') function getDbPath() { if (process.env.DATABASE_PATH && process.env.DATABASE_PATH.length > 0) { return process.env.DATABASE_PATH } // default dev DB in backend folder return path.join(__dirname, '..', 'skillmate.dev.encrypted.db') } function ensureTables(db) { // controlled_vocabulary (canonical definition from secureDatabase) db.exec(` CREATE TABLE IF NOT EXISTS controlled_vocabulary ( id TEXT PRIMARY KEY, category TEXT NOT NULL, value TEXT NOT NULL, description TEXT, is_active INTEGER DEFAULT 1, created_at TEXT NOT NULL, UNIQUE(category, value) ); CREATE INDEX IF NOT EXISTS idx_vocab_category ON controlled_vocabulary(category); CREATE INDEX IF NOT EXISTS idx_vocab_value ON controlled_vocabulary(value); `) // skills (canonical definition from secureDatabase) db.exec(` CREATE TABLE IF NOT EXISTS skills ( id TEXT PRIMARY KEY, name TEXT NOT NULL, category TEXT NOT NULL, description TEXT, requires_certification INTEGER DEFAULT 0, expires_after INTEGER ) `) } function loadHierarchy() { // Load from shared/skills.js const skillsPath = path.join(__dirname, '..', '..', 'shared', 'skills.js') if (!fs.existsSync(skillsPath)) { throw new Error('shared/skills.js not found') } const mod = require(skillsPath) if (!mod || !Array.isArray(mod.SKILL_HIERARCHY)) { throw new Error('SKILL_HIERARCHY missing or invalid in shared/skills.js') } return mod.SKILL_HIERARCHY } function seed(db, hierarchy) { const now = new Date().toISOString() const insertVocab = db.prepare(` INSERT OR IGNORE INTO controlled_vocabulary (id, category, value, description, is_active, created_at) VALUES (?, ?, ?, ?, ?, ?) `) const insertSkill = db.prepare(` INSERT OR IGNORE INTO skills (id, name, category, description, requires_certification, expires_after) VALUES (?, ?, ?, ?, ?, ?) `) let catCount = 0 let subCount = 0 let skillCount = 0 for (const cat of hierarchy) { const catId = String(cat.id) const catName = String(cat.name || cat.id) // category const catPk = crypto.randomUUID() const catRes = insertVocab.run(catPk, 'skill_category', catId, catName, 1, now) if (catRes.changes > 0) catCount++ for (const sub of (cat.subcategories || [])) { const subId = String(sub.id) const subName = String(sub.name || sub.id) const key = `${catId}.${subId}` // subcategory const subPk = crypto.randomUUID() const subRes = insertVocab.run(subPk, 'skill_subcategory', key, subName, 1, now) if (subRes.changes > 0) subCount++ for (const sk of (sub.skills || [])) { const sId = `${key}.${sk.id}` const sName = String(sk.name || sk.id) const requires = (catId === 'certifications' || subId === 'weapons') ? 1 : 0 const expires = (catId === 'certifications') ? 36 : null const sRes = insertSkill.run(sId, sName, key, null, requires, expires) if (sRes.changes > 0) skillCount++ } } } return { catCount, subCount, skillCount } } function main() { const dbPath = getDbPath() console.log('➡️ Seeding skills into DB:', dbPath) const db = new Database(dbPath) try { ensureTables(db) const hierarchy = loadHierarchy() const { catCount, subCount, skillCount } = seed(db, hierarchy) console.log(`✅ Seeding completed. Inserted: ${catCount} categories, ${subCount} subcategories, ${skillCount} skills.`) console.log('ℹ️ Re-open the Admin Panel to see Skills in hierarchy view.') } catch (err) { console.error('❌ Seeding failed:', err.message) process.exitCode = 1 } finally { try { db.close() } catch {} } } main()