#!/usr/bin/env node 'use strict'; const readline = require('readline'); const bcrypt = require('bcryptjs'); const { initDatabase } = require('./src/database'); const { User } = require('./src/models'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); function ask(question) { return new Promise(resolve => rl.question(question, resolve)); } function askHidden(question) { return new Promise(resolve => { process.stdout.write(question); const stdin = process.stdin; const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdin.setEncoding('utf8'); let input = ''; const onData = ch => { if (ch === '\r' || ch === '\n') { stdin.setRawMode(wasRaw || false); stdin.pause(); stdin.removeListener('data', onData); process.stdout.write('\n'); resolve(input); } else if (ch === '\u0003') { process.stdout.write('\n'); process.exit(); } else if (ch === '\u007f' || ch === '\b') { if (input.length > 0) input = input.slice(0, -1); } else { input += ch; } }; stdin.on('data', onData); }); } function formatDate(d) { return d ? new Date(d).toISOString().slice(0, 10) : '-'; } function printUsers(users) { console.log(''); const header = ` ${'ID'.padEnd(4)} ${'Login'.padEnd(20)} ${'Email'.padEnd(28)} ${'Active'.padEnd(6)} ${'Admin'.padEnd(5)} Created`; console.log(header); console.log('-'.repeat(header.length)); for (const u of users) { console.log( ` ${String(u.id).padEnd(4)} ${u.login.padEnd(20)} ${(u.email || '').padEnd(28)} ${(u.isActive ? 'yes' : 'no').padEnd(6)} ${(u.isAdmin ? 'yes' : 'no').padEnd(5)} ${formatDate(u.createdAt)}` ); } console.log(''); } async function listUsers() { const users = await User.findAll({ order: [['id', 'ASC']] }); if (users.length === 0) { console.log('No users found.'); } else { printUsers(users); } } async function findUser(prompt) { const input = (await ask(prompt)).trim(); if (!input) return null; const byId = /^\d+$/.test(input); const user = byId ? await User.findByPk(parseInt(input)) : await User.findOne({ where: { login: input } }); if (!user) { console.log('User not found.'); return null; } return user; } async function activateDeactivate() { await listUsers(); const user = await findUser('Enter user ID or login: '); if (!user) return; const current = user.isActive ? 'active' : 'inactive'; const action = user.isActive ? 'deactivate' : 'activate'; const confirm = (await ask(`User "${user.login}" is ${current}. ${action}? [y/N] `)).trim().toLowerCase(); if (confirm !== 'y') { console.log('Cancelled.'); return; } await user.update({ isActive: !user.isActive }); console.log(`User "${user.login}" is now ${user.isActive ? 'active' : 'inactive'}.`); } async function changePassword() { await listUsers(); const user = await findUser('Enter user ID or login: '); if (!user) return; const pw1 = await askHidden(`New password for "${user.login}": `); if (!pw1) { console.log('Password cannot be empty.'); return; } const pw2 = await askHidden('Confirm password: '); if (pw1 !== pw2) { console.log('Passwords do not match.'); return; } const hash = await bcrypt.hash(pw1, 10); await user.update({ passwordHash: hash }); console.log(`Password updated for "${user.login}".`); } async function toggleAdmin() { await listUsers(); const user = await findUser('Enter user ID or login: '); if (!user) return; const current = user.isAdmin ? 'admin' : 'regular user'; const action = user.isAdmin ? 'remove admin rights from' : 'grant admin rights to'; const confirm = (await ask(`User "${user.login}" is ${current}. ${action}? [y/N] `)).trim().toLowerCase(); if (confirm !== 'y') { console.log('Cancelled.'); return; } await user.update({ isAdmin: !user.isAdmin }); console.log(`User "${user.login}" is now ${user.isAdmin ? 'an admin' : 'a regular user'}.`); } async function createUser() { const login = (await ask('Login: ')).trim(); if (!login) { console.log('Login cannot be empty.'); return; } const existing = await User.findOne({ where: { login } }); if (existing) { console.log('Login already taken.'); return; } const email = (await ask('Email (optional): ')).trim() || null; const pw = await askHidden('Password: '); if (!pw) { console.log('Password cannot be empty.'); return; } const pw2 = await askHidden('Confirm password: '); if (pw !== pw2) { console.log('Passwords do not match.'); return; } const isAdmin = (await ask('Admin? [y/N] ')).trim().toLowerCase() === 'y'; const isActive = (await ask('Active? [Y/n] ')).trim().toLowerCase() !== 'n'; const hash = await bcrypt.hash(pw, 10); const user = await User.create({ login, email, passwordHash: hash, isAdmin, isActive }); console.log(`User "${user.login}" created (id=${user.id}).`); } async function deleteUser() { await listUsers(); const user = await findUser('Enter user ID or login to delete: '); if (!user) return; const confirm = (await ask(`Delete user "${user.login}" and all their data? [y/N] `)).trim().toLowerCase(); if (confirm !== 'y') { console.log('Cancelled.'); return; } await user.destroy(); console.log(`User "${user.login}" deleted.`); } const MENU = [ { key: '1', label: 'List users', fn: listUsers }, { key: '2', label: 'Activate / deactivate', fn: activateDeactivate }, { key: '3', label: 'Change password', fn: changePassword }, { key: '4', label: 'Toggle admin', fn: toggleAdmin }, { key: '5', label: 'Create user', fn: createUser }, { key: '6', label: 'Delete user', fn: deleteUser }, { key: 'q', label: 'Quit', fn: null }, ]; function printMenu() { console.log('\n=== GPX-Vis User Manager ==='); for (const item of MENU) { console.log(` [${item.key}] ${item.label}`); } } async function main() { await initDatabase(); console.log('Connected to database.'); while (true) { printMenu(); const choice = (await ask('> ')).trim().toLowerCase(); const item = MENU.find(m => m.key === choice); if (!item) { console.log('Invalid choice.'); continue; } if (!item.fn) break; try { await item.fn(); } catch (e) { console.error('Error:', e.message); } } rl.close(); process.exit(0); } main().catch(e => { console.error(e); process.exit(1); });