| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- #!/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); });
|