- 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
135 lines
5.2 KiB
JavaScript
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;
|