Files
scrum-manager/server/routes/auth.js
tusuii 0fa2302b26 feat: employee management — add/delete users from Members page
- Backend: POST /api/auth/users (create user), DELETE /api/auth/users/:id (delete user, unassign tasks)
- Frontend API: apiCreateUser, apiDeleteUser
- MembersPage: working Add Employee modal (name/email/password/role/dept), delete button with confirmation
- Only CEO/CTO/Manager roles see management controls
- CSS: btn-danger, btn-danger-sm styles
2026-02-16 12:48:20 +05:30

135 lines
5.2 KiB
JavaScript

import { Router } from 'express';
import bcrypt from 'bcryptjs';
import pool from '../db.js';
import { randomUUID } from 'crypto';
const router = Router();
// POST /api/auth/login
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email and password required' });
}
const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [email]);
if (rows.length === 0) {
return res.status(401).json({ error: 'Invalid email or password' });
}
const user = rows[0];
const valid = await bcrypt.compare(password, user.password_hash);
if (!valid) {
return res.status(401).json({ error: 'Invalid email or password' });
}
res.json({
id: user.id, name: user.name, role: user.role, email: user.email,
color: user.color, avatar: user.avatar, dept: user.dept,
});
} catch (err) {
console.error('Login error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
// POST /api/auth/register
router.post('/register', async (req, res) => {
try {
const { name, email, password, role, dept } = req.body;
if (!name || !email || !password) {
return res.status(400).json({ error: 'Name, email and password required' });
}
const [existing] = await pool.query('SELECT id FROM users WHERE email = ?', [email]);
if (existing.length > 0) {
return res.status(409).json({ error: 'Email already registered' });
}
const id = randomUUID();
const password_hash = await bcrypt.hash(password, 10);
const avatar = name.split(' ').map(w => w[0]).join('').substring(0, 2).toUpperCase();
const colors = ['#818cf8', '#f59e0b', '#34d399', '#f472b6', '#fb923c', '#60a5fa', '#a78bfa'];
const color = colors[Math.floor(Math.random() * colors.length)];
await pool.query(
'INSERT INTO users (id, name, role, email, password_hash, color, avatar, dept) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[id, name, role || 'employee', email, password_hash, color, avatar, dept || '']
);
res.status(201).json({ id, name, role: role || 'employee', email, color, avatar, dept: dept || '' });
} catch (err) {
console.error('Register error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
// GET /api/auth/users
router.get('/users', async (_req, res) => {
try {
const [rows] = await pool.query('SELECT id, name, role, email, color, avatar, dept FROM users');
res.json(rows);
} catch (err) {
console.error('Get users error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
// POST /api/auth/users — Admin-create a new user (manager/cto/ceo)
router.post('/users', async (req, res) => {
try {
const { name, email, password, role, dept } = req.body;
if (!name || !email || !password) {
return res.status(400).json({ error: 'Name, email and password required' });
}
const [existing] = await pool.query('SELECT id FROM users WHERE email = ?', [email]);
if (existing.length > 0) {
return res.status(409).json({ error: 'Email already registered' });
}
const id = randomUUID();
const password_hash = await bcrypt.hash(password, 10);
const avatar = name.split(' ').map(w => w[0]).join('').substring(0, 2).toUpperCase();
const colors = ['#818cf8', '#f59e0b', '#34d399', '#f472b6', '#fb923c', '#60a5fa', '#a78bfa'];
const color = colors[Math.floor(Math.random() * colors.length)];
await pool.query(
'INSERT INTO users (id, name, role, email, password_hash, color, avatar, dept) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[id, name, role || 'employee', email, password_hash, color, avatar, dept || '']
);
res.status(201).json({ id, name, role: role || 'employee', email, color, avatar, dept: dept || '' });
} catch (err) {
console.error('Create user error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
// DELETE /api/auth/users/:id — Delete a user (unassign their tasks first)
router.delete('/users/:id', async (req, res) => {
try {
const { id } = req.params;
const [user] = await pool.query('SELECT id FROM users WHERE id = ?', [id]);
if (user.length === 0) {
return res.status(404).json({ error: 'User not found' });
}
// Unassign tasks assigned to or reported by this user
await pool.query('UPDATE tasks SET assignee_id = NULL WHERE assignee_id = ?', [id]);
await pool.query('UPDATE tasks SET reporter_id = NULL WHERE reporter_id = ?', [id]);
// Delete the user (cascading will handle comments, etc. via ON DELETE SET NULL)
await pool.query('DELETE FROM users WHERE id = ?', [id]);
res.json({ success: true, id });
} catch (err) {
console.error('Delete user error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
export default router;