Zwiscshenstand - laufende Version
Dieser Commit ist enthalten in:
112
backend/src/services/skillSeeder.ts
Normale Datei
112
backend/src/services/skillSeeder.ts
Normale Datei
@ -0,0 +1,112 @@
|
||||
import path from 'path'
|
||||
import { db } from '../config/secureDatabase'
|
||||
import { logger } from '../utils/logger'
|
||||
|
||||
type SkillHierarchy = Array<{
|
||||
id: string
|
||||
name: string
|
||||
subcategories?: Array<{
|
||||
id: string
|
||||
name: string
|
||||
skills?: Array<{ id: string; name: string }>
|
||||
}>
|
||||
}>
|
||||
|
||||
function loadSharedHierarchy(): SkillHierarchy | null {
|
||||
try {
|
||||
const sharedPath = path.join(__dirname, '../../../shared/skills.js')
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const mod = require(sharedPath)
|
||||
if (mod && Array.isArray(mod.SKILL_HIERARCHY)) {
|
||||
return mod.SKILL_HIERARCHY as SkillHierarchy
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('SkillSeeder: Could not load shared/skills.js. Skipping seeding.')
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function ensureSkillsSeeded() {
|
||||
try {
|
||||
// Always ensure vocabulary tables exist (idempotent)
|
||||
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);
|
||||
`)
|
||||
|
||||
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
|
||||
)
|
||||
`)
|
||||
|
||||
const hierarchy = loadSharedHierarchy()
|
||||
if (!hierarchy) return
|
||||
|
||||
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 cats = 0, subs = 0, skills = 0
|
||||
|
||||
for (const cat of hierarchy) {
|
||||
const catId = String(cat.id)
|
||||
const catName = String(cat.name || cat.id)
|
||||
|
||||
// category
|
||||
insertVocab.run(cryptoRandomUUID(), 'skill_category', catId, catName, 1, now)
|
||||
cats++
|
||||
|
||||
for (const sub of (cat.subcategories || [])) {
|
||||
const subId = String(sub.id)
|
||||
const subName = String(sub.name || sub.id)
|
||||
const key = `${catId}.${subId}`
|
||||
insertVocab.run(cryptoRandomUUID(), 'skill_subcategory', key, subName, 1, now)
|
||||
subs++
|
||||
|
||||
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
|
||||
insertSkill.run(sId, sName, key, null, requires, expires)
|
||||
skills++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`SkillSeeder: ensured ${cats} categories, ${subs} subcategories, ${skills} skills (idempotent).`)
|
||||
} catch (err) {
|
||||
logger.warn('SkillSeeder failed (non-fatal): ' + (err as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
function cryptoRandomUUID() {
|
||||
// Node 18 has crypto.randomUUID, but keep a tiny fallback
|
||||
try { return (require('crypto').randomUUID() as string) } catch {}
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8)
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren