Initial commit
Dieser Commit ist enthalten in:
71
backend/scripts/migrate-users.js
Normale Datei
71
backend/scripts/migrate-users.js
Normale Datei
@ -0,0 +1,71 @@
|
||||
// Migrate users from legacy unencrypted DB (skillmate.dev.db)
|
||||
// to encrypted dev DB (skillmate.dev.encrypted.db)
|
||||
// Usage: from backend dir run: npm run migrate-users
|
||||
|
||||
const path = require('path')
|
||||
const Database = require('better-sqlite3')
|
||||
const CryptoJS = require('crypto-js')
|
||||
require('dotenv').config()
|
||||
|
||||
function encKey() {
|
||||
return process.env.FIELD_ENCRYPTION_KEY || 'dev_field_key_change_in_production_32chars_min!'
|
||||
}
|
||||
|
||||
function encrypt(text) {
|
||||
if (!text) return null
|
||||
return CryptoJS.AES.encrypt(text, encKey()).toString()
|
||||
}
|
||||
|
||||
function sha256Lower(text) {
|
||||
return CryptoJS.SHA256((text || '').toLowerCase()).toString()
|
||||
}
|
||||
|
||||
function main() {
|
||||
const legacyPath = path.join(process.cwd(), 'skillmate.dev.db')
|
||||
const encPath = path.join(process.cwd(), 'skillmate.dev.encrypted.db')
|
||||
|
||||
const legacy = new Database(legacyPath)
|
||||
const enc = new Database(encPath)
|
||||
|
||||
try {
|
||||
const legacyUsers = legacy.prepare('SELECT id, username, email, password, role, employee_id, last_login, is_active, created_at, updated_at FROM users').all()
|
||||
let migrated = 0
|
||||
const existsByUsername = enc.prepare('SELECT id FROM users WHERE username = ?')
|
||||
|
||||
const insert = enc.prepare(`
|
||||
INSERT INTO users (id, username, email, email_hash, password, role, employee_id, last_login, is_active, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
|
||||
for (const u of legacyUsers) {
|
||||
const exists = existsByUsername.get(u.username)
|
||||
if (exists) continue
|
||||
|
||||
insert.run(
|
||||
u.id,
|
||||
u.username,
|
||||
encrypt(u.email),
|
||||
sha256Lower(u.email || ''),
|
||||
u.password,
|
||||
u.role,
|
||||
u.employee_id || null,
|
||||
u.last_login || null,
|
||||
u.is_active ?? 1,
|
||||
u.created_at || new Date().toISOString(),
|
||||
u.updated_at || new Date().toISOString()
|
||||
)
|
||||
migrated++
|
||||
}
|
||||
|
||||
console.log(`✅ Migration abgeschlossen. Übertragene Benutzer: ${migrated}`)
|
||||
} catch (err) {
|
||||
console.error('❌ Migration fehlgeschlagen:', err)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
legacy.close()
|
||||
enc.close()
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
88
backend/scripts/purge-users.js
Normale Datei
88
backend/scripts/purge-users.js
Normale Datei
@ -0,0 +1,88 @@
|
||||
// Purge users from DB, keeping only 'admin' and a specific email
|
||||
// Usage (Windows CMD/PowerShell from backend directory):
|
||||
// npm run purge-users -- --email hendrik.gebhardt@polizei.nrw.de
|
||||
// If --email is omitted, defaults to 'hendrik.gebhardt@polizei.nrw.de'
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const Database = require('better-sqlite3')
|
||||
const CryptoJS = require('crypto-js')
|
||||
|
||||
function getDbPath() {
|
||||
const envPath = process.env.DATABASE_PATH
|
||||
if (envPath && envPath.trim()) return envPath
|
||||
const prod = process.env.NODE_ENV === 'production'
|
||||
return prod
|
||||
? path.join(process.cwd(), 'data', 'skillmate.encrypted.db')
|
||||
: path.join(process.cwd(), 'skillmate.dev.encrypted.db')
|
||||
}
|
||||
|
||||
function hashLower(text) {
|
||||
return CryptoJS.SHA256(String(text || '').toLowerCase()).toString()
|
||||
}
|
||||
|
||||
function parseEmailArg() {
|
||||
const idx = process.argv.indexOf('--email')
|
||||
if (idx !== -1 && process.argv[idx + 1]) return process.argv[idx + 1]
|
||||
return 'hendrik.gebhardt@polizei.nrw.de'
|
||||
}
|
||||
|
||||
function backupFile(filePath) {
|
||||
try {
|
||||
const dir = path.dirname(filePath)
|
||||
const base = path.basename(filePath)
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0,19)
|
||||
const dest = path.join(dir, `${base}.backup_${ts}`)
|
||||
fs.copyFileSync(filePath, dest)
|
||||
console.log(`📦 Backup erstellt: ${dest}`)
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Konnte kein Backup erstellen:', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const dbPath = getDbPath()
|
||||
const keepEmail = parseEmailArg()
|
||||
const keepHash = hashLower(keepEmail)
|
||||
|
||||
console.log(`Datenbank: ${dbPath}`)
|
||||
console.log(`Behalte Nutzer: 'admin' und ${keepEmail}`)
|
||||
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
console.error('❌ Datenbankdatei nicht gefunden.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
backupFile(dbPath)
|
||||
|
||||
const db = new Database(dbPath)
|
||||
try {
|
||||
const all = db.prepare('SELECT id, username, email_hash FROM users').all()
|
||||
const keep = []
|
||||
const del = []
|
||||
for (const u of all) {
|
||||
if (u.username === 'admin') { keep.push(u); continue }
|
||||
if (u.email_hash && u.email_hash === keepHash) { keep.push(u); continue }
|
||||
del.push(u)
|
||||
}
|
||||
|
||||
console.log(`Gefundene Nutzer: ${all.length}`)
|
||||
console.log(`Behalte: ${keep.length} | Lösche: ${del.length}`)
|
||||
|
||||
const tx = db.transaction(() => {
|
||||
const delStmt = db.prepare('DELETE FROM users WHERE id = ?')
|
||||
for (const u of del) delStmt.run(u.id)
|
||||
})
|
||||
tx()
|
||||
|
||||
console.log('✅ Bereinigung abgeschlossen.')
|
||||
} catch (err) {
|
||||
console.error('❌ Fehler bei der Bereinigung:', err)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
76
backend/scripts/reset-admin.js
Normale Datei
76
backend/scripts/reset-admin.js
Normale Datei
@ -0,0 +1,76 @@
|
||||
// Reset the admin login to username 'admin' with password 'admin123'
|
||||
// Usage: from backend directory, run `npm run reset-admin` or `node scripts/reset-admin.js`
|
||||
|
||||
const path = require('path')
|
||||
const Database = require('better-sqlite3')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const CryptoJS = require('crypto-js')
|
||||
require('dotenv').config()
|
||||
|
||||
function getDbPath() {
|
||||
const envPath = process.env.DATABASE_PATH
|
||||
if (envPath && envPath.trim()) return envPath
|
||||
const prod = process.env.NODE_ENV === 'production'
|
||||
return prod
|
||||
? path.join(process.cwd(), 'data', 'skillmate.encrypted.db')
|
||||
: path.join(process.cwd(), 'skillmate.dev.encrypted.db')
|
||||
}
|
||||
|
||||
function getFieldKey() {
|
||||
return (
|
||||
process.env.FIELD_ENCRYPTION_KEY ||
|
||||
'dev_field_key_change_in_production_32chars_min!'
|
||||
)
|
||||
}
|
||||
|
||||
function encrypt(text) {
|
||||
if (!text) return null
|
||||
return CryptoJS.AES.encrypt(text, getFieldKey()).toString()
|
||||
}
|
||||
|
||||
function sha256Lower(text) {
|
||||
return CryptoJS.SHA256(text.toLowerCase()).toString()
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const dbPath = getDbPath()
|
||||
console.log(`Using database: ${dbPath}`)
|
||||
const db = new Database(dbPath)
|
||||
|
||||
try {
|
||||
const admin = db.prepare('SELECT id, username FROM users WHERE username = ?').get('admin')
|
||||
const newHash = await bcrypt.hash('admin123', 12)
|
||||
|
||||
if (admin) {
|
||||
db.prepare('UPDATE users SET password = ?, is_active = 1, role = ?, updated_at = ? WHERE id = ?')
|
||||
.run(newHash, 'admin', new Date().toISOString(), admin.id)
|
||||
console.log('✅ Admin-Passwort zurückgesetzt: admin / admin123')
|
||||
} else {
|
||||
const now = new Date().toISOString()
|
||||
const email = 'admin@skillmate.local'
|
||||
db.prepare(`
|
||||
INSERT INTO users (id, username, email, email_hash, password, role, is_active, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
'admin-' + Date.now(),
|
||||
'admin',
|
||||
encrypt(email),
|
||||
sha256Lower(email),
|
||||
newHash,
|
||||
'admin',
|
||||
1,
|
||||
now,
|
||||
now
|
||||
)
|
||||
console.log('✅ Admin-Benutzer erstellt: admin / admin123')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('❌ Fehler beim Zurücksetzen der Admin-Anmeldeinformationen:', err)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
64
backend/scripts/seed-skills-from-frontend.js
Normale Datei
64
backend/scripts/seed-skills-from-frontend.js
Normale Datei
@ -0,0 +1,64 @@
|
||||
// Seed skills table from frontend's SKILL_HIERARCHY definition
|
||||
// Usage: from backend dir: npm run seed-skills
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const vm = require('vm')
|
||||
const Database = require('better-sqlite3')
|
||||
|
||||
function parseFrontendHierarchy() {
|
||||
const tsPath = path.join(process.cwd(), '..', 'frontend', 'src', 'data', 'skillCategories.ts')
|
||||
const src = fs.readFileSync(tsPath, 'utf8')
|
||||
// Remove interface declarations and LANGUAGE_LEVELS export, keep the array literal
|
||||
let code = src
|
||||
.replace(/export interface[\s\S]*?\n\}/g, '')
|
||||
.replace(/export const LANGUAGE_LEVELS[\s\S]*?\n\n/, '')
|
||||
.replace(/export const SKILL_HIERARCHY:[^=]*=/, 'module.exports =')
|
||||
|
||||
const sandbox = { module: {}, exports: {} }
|
||||
vm.createContext(sandbox)
|
||||
vm.runInContext(code, sandbox)
|
||||
return sandbox.module.exports || sandbox.exports
|
||||
}
|
||||
|
||||
function main() {
|
||||
const dbPath = path.join(process.cwd(), 'skillmate.dev.encrypted.db')
|
||||
const db = new Database(dbPath)
|
||||
try {
|
||||
const hierarchy = parseFrontendHierarchy()
|
||||
if (!Array.isArray(hierarchy)) {
|
||||
throw new Error('Parsed hierarchy is not an array')
|
||||
}
|
||||
|
||||
const insert = db.prepare(`
|
||||
INSERT OR IGNORE INTO skills (id, name, category, description, requires_certification, expires_after)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
|
||||
let count = 0
|
||||
for (const cat of hierarchy) {
|
||||
for (const sub of (cat.subcategories || [])) {
|
||||
const categoryKey = `${cat.id}.${sub.id}`
|
||||
for (const sk of (sub.skills || [])) {
|
||||
const id = `${categoryKey}.${sk.id}`
|
||||
const name = sk.name
|
||||
const description = null
|
||||
const requires = cat.id === 'certifications' || sub.id === 'weapons' ? 1 : 0
|
||||
const expires = cat.id === 'certifications' ? 36 : null
|
||||
const res = insert.run(id, name, categoryKey, description, requires, expires)
|
||||
if (res.changes > 0) count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Seed abgeschlossen. Neue Skills eingefügt: ${count}`)
|
||||
} catch (err) {
|
||||
console.error('❌ Seed fehlgeschlagen:', err)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren