Files
oikos/setup.js
T
ulsklyc d49cbe33b3 feat: Phase 1 — Projektstruktur, DB-Schema, Auth-System
- Vollständige Verzeichnisstruktur gemäß CLAUDE.md
- Express-Server mit Helmet, Sessions, Rate Limiting, SPA-Fallback
- SQLite-Schema (Migration v1): 10 Tabellen, updated_at-Triggers, Indizes
- Versioniertes Migrations-System (schema_migrations)
- Auth-Routen: Login, Logout, /me, Admin-User-CRUD
- Frontend App-Shell: SPA-Router, API-Client, Design-System (CSS Tokens)
- PWA: Service Worker, Web App Manifest
- Setup-Script für ersten Admin-User (node setup.js)
- DB-Tests mit node:sqlite built-in: 29/29 bestanden
- Docker Compose + Dockerfile + Nginx-Beispielkonfiguration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 14:32:36 +01:00

137 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Modul: Setup-Script
* Zweck: Erstmalige Einrichtung — ersten Admin-User anlegen.
* Wird einmalig nach dem ersten Start ausgeführt: `node setup.js`
* Abhängigkeiten: server/db.js, bcrypt, dotenv
*/
'use strict';
require('dotenv').config();
const readline = require('node:readline');
const bcrypt = require('bcrypt');
const db = require('./server/db');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function prompt(question) {
return new Promise((resolve) => rl.question(question, resolve));
}
function promptPassword(question) {
return new Promise((resolve) => {
process.stdout.write(question);
process.stdin.setRawMode(true);
process.stdin.resume();
let password = '';
process.stdin.on('data', function handler(char) {
char = char.toString();
if (char === '\r' || char === '\n') {
process.stdin.setRawMode(false);
process.stdin.removeListener('data', handler);
process.stdout.write('\n');
resolve(password);
} else if (char === '\u0003') {
process.exit();
} else if (char === '\u007f') {
if (password.length > 0) {
password = password.slice(0, -1);
process.stdout.write('\b \b');
}
} else {
password += char;
process.stdout.write('*');
}
});
});
}
async function main() {
console.log('\n=== Oikos Setup ===\n');
// Datenbank initialisieren
db.init();
// Prüfen ob bereits Admin vorhanden
const existingAdmin = db.get()
.prepare("SELECT id FROM users WHERE role = 'admin' LIMIT 1")
.get();
if (existingAdmin) {
console.log(' Es existiert bereits ein Admin-Account.\n');
const proceed = await prompt('Trotzdem einen weiteren Admin anlegen? (j/N): ');
if (proceed.toLowerCase() !== 'j') {
console.log('Setup abgebrochen.');
rl.close();
process.exit(0);
}
}
console.log('Admin-Account anlegen:\n');
const username = (await prompt('Benutzername: ')).trim();
if (!username || username.length < 3) {
console.error('Fehler: Benutzername muss mindestens 3 Zeichen lang sein.');
process.exit(1);
}
const displayName = (await prompt('Anzeigename (z.B. "Max Mustermann"): ')).trim();
if (!displayName) {
console.error('Fehler: Anzeigename darf nicht leer sein.');
process.exit(1);
}
const password = await promptPassword('Passwort: ');
if (password.length < 8) {
console.error('Fehler: Passwort muss mindestens 8 Zeichen lang sein.');
process.exit(1);
}
const passwordConfirm = await promptPassword('Passwort bestätigen: ');
if (password !== passwordConfirm) {
console.error('Fehler: Passwörter stimmen nicht überein.');
process.exit(1);
}
const avatarColors = ['#007AFF', '#34C759', '#FF9500', '#FF3B30', '#AF52DE', '#FF2D55'];
const avatarColor = avatarColors[Math.floor(Math.random() * avatarColors.length)];
console.log('\nAccount wird erstellt …');
const hash = await bcrypt.hash(password, 12);
try {
const result = db.get()
.prepare(`
INSERT INTO users (username, display_name, password_hash, avatar_color, role)
VALUES (?, ?, ?, ?, 'admin')
`)
.run(username, displayName, hash, avatarColor);
console.log(`\n✓ Admin-Account erstellt (ID: ${result.lastInsertRowid})`);
console.log(` Benutzername: ${username}`);
console.log(` Anzeigename: ${displayName}`);
console.log(` Rolle: admin`);
console.log('\nDu kannst dich jetzt unter /login anmelden.\n');
} catch (err) {
if (err.message?.includes('UNIQUE constraint')) {
console.error(`\nFehler: Benutzername "${username}" ist bereits vergeben.`);
} else {
console.error('\nFehler beim Erstellen:', err.message);
}
process.exit(1);
}
rl.close();
process.exit(0);
}
main().catch((err) => {
console.error('Unerwarteter Fehler:', err.message);
process.exit(1);
});