feat(postgres): migrate all route files from SQLite to pg pool
- All 16 route files now import pool from ../db directly
- Removed db parameter from all factory functions
- All callbacks replaced with async/await pool.query()
- All ? placeholders converted to $1, $2... numbered params
- datetime('now') → NOW(), INSERT OR IGNORE → ON CONFLICT DO NOTHING
- LIKE → ILIKE for case-insensitive searches
- Error detection: err.code === '23505' for unique violations
- server.js no longer passes pool/db/requireAuth to route factories
- Only ivantiFindings.js still receives pool (pending task 8 rewrite)
This commit is contained in:
@@ -1,27 +1,22 @@
|
||||
// User Management Routes (Admin only)
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const pool = require('../db');
|
||||
const { validateTeams } = require('../helpers/teams');
|
||||
|
||||
function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
function createUsersRouter(requireAuth, requireGroup, logAudit) {
|
||||
const router = express.Router();
|
||||
|
||||
// All routes require Admin group
|
||||
router.use(requireAuth(db), requireGroup('Admin'));
|
||||
router.use(requireAuth(), requireGroup('Admin'));
|
||||
|
||||
// Get all users
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const users = await new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT id, username, email, user_group AS 'group', bu_teams, is_active, created_at, last_login
|
||||
FROM users ORDER BY created_at DESC`,
|
||||
(err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(rows);
|
||||
}
|
||||
);
|
||||
});
|
||||
const { rows: users } = await pool.query(
|
||||
`SELECT id, username, email, user_group AS "group", bu_teams, is_active, created_at, last_login
|
||||
FROM users ORDER BY created_at DESC`
|
||||
);
|
||||
// Parse bu_teams into teams array for each user
|
||||
const usersWithTeams = users.map(u => ({
|
||||
...u,
|
||||
@@ -37,17 +32,13 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
// Get single user
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const user = await new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
`SELECT id, username, email, user_group AS 'group', bu_teams, is_active, created_at, last_login
|
||||
FROM users WHERE id = ?`,
|
||||
[req.params.id],
|
||||
(err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
}
|
||||
);
|
||||
});
|
||||
const { rows } = await pool.query(
|
||||
`SELECT id, username, email, user_group AS "group", bu_teams, is_active, created_at, last_login
|
||||
FROM users WHERE id = $1`,
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
const user = rows[0];
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
@@ -90,19 +81,16 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
try {
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`INSERT INTO users (username, email, password_hash, user_group, bu_teams)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[username, email, passwordHash, userGroup, teamsStr],
|
||||
function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ id: this.lastID });
|
||||
}
|
||||
);
|
||||
});
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO users (username, email, password_hash, user_group, bu_teams)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id`,
|
||||
[username, email, passwordHash, userGroup, teamsStr]
|
||||
);
|
||||
|
||||
logAudit(db, {
|
||||
const result = rows[0];
|
||||
|
||||
logAudit({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
action: 'user_create',
|
||||
@@ -125,7 +113,7 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Create user error:', err);
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
if (err.code === '23505') { // Postgres unique violation
|
||||
return res.status(409).json({ error: 'Username or email already exists' });
|
||||
}
|
||||
res.status(500).json({ error: 'Failed to create user' });
|
||||
@@ -165,16 +153,12 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
|
||||
try {
|
||||
// Fetch current user record before update (needed for group change audit)
|
||||
const currentUser = await new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
'SELECT user_group, bu_teams FROM users WHERE id = ?',
|
||||
[userId],
|
||||
(err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
}
|
||||
);
|
||||
});
|
||||
const { rows: currentRows } = await pool.query(
|
||||
'SELECT user_group, bu_teams FROM users WHERE id = $1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
const currentUser = currentRows[0];
|
||||
|
||||
if (!currentUser) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
@@ -182,30 +166,31 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
|
||||
const updates = [];
|
||||
const values = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (username) {
|
||||
updates.push('username = ?');
|
||||
updates.push(`username = $${paramIndex++}`);
|
||||
values.push(username);
|
||||
}
|
||||
if (email) {
|
||||
updates.push('email = ?');
|
||||
updates.push(`email = $${paramIndex++}`);
|
||||
values.push(email);
|
||||
}
|
||||
if (password) {
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
updates.push('password_hash = ?');
|
||||
updates.push(`password_hash = $${paramIndex++}`);
|
||||
values.push(passwordHash);
|
||||
}
|
||||
if (group) {
|
||||
updates.push('user_group = ?');
|
||||
updates.push(`user_group = $${paramIndex++}`);
|
||||
values.push(group);
|
||||
}
|
||||
if (typeof is_active === 'boolean') {
|
||||
updates.push('is_active = ?');
|
||||
values.push(is_active ? 1 : 0);
|
||||
updates.push(`is_active = $${paramIndex++}`);
|
||||
values.push(is_active);
|
||||
}
|
||||
if (typeof bu_teams === 'string') {
|
||||
updates.push('bu_teams = ?');
|
||||
updates.push(`bu_teams = $${paramIndex++}`);
|
||||
values.push(bu_teams);
|
||||
}
|
||||
|
||||
@@ -215,16 +200,10 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
|
||||
values.push(userId);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`UPDATE users SET ${updates.join(', ')} WHERE id = ?`,
|
||||
values,
|
||||
function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ changes: this.changes });
|
||||
}
|
||||
);
|
||||
});
|
||||
await pool.query(
|
||||
`UPDATE users SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
|
||||
values
|
||||
);
|
||||
|
||||
const updatedFields = {};
|
||||
if (username) updatedFields.username = username;
|
||||
@@ -234,7 +213,7 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
if (password) updatedFields.password_changed = true;
|
||||
if (typeof bu_teams === 'string') updatedFields.bu_teams = bu_teams;
|
||||
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
action: 'user_update',
|
||||
@@ -246,7 +225,7 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
|
||||
// Log specific audit entry for group changes
|
||||
if (group && group !== currentUser.user_group) {
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
action: 'user_group_change',
|
||||
@@ -262,7 +241,7 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
|
||||
// Log specific audit entry for bu_teams changes
|
||||
if (typeof bu_teams === 'string' && bu_teams !== (currentUser.bu_teams || '')) {
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
action: 'user_teams_change',
|
||||
@@ -278,15 +257,13 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
|
||||
// If user was deactivated, delete their sessions
|
||||
if (is_active === false) {
|
||||
await new Promise((resolve) => {
|
||||
db.run('DELETE FROM sessions WHERE user_id = ?', [userId], () => resolve());
|
||||
});
|
||||
await pool.query('DELETE FROM sessions WHERE user_id = $1', [userId]);
|
||||
}
|
||||
|
||||
res.json({ message: 'User updated successfully' });
|
||||
} catch (err) {
|
||||
console.error('Update user error:', err);
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
if (err.code === '23505') { // Postgres unique violation
|
||||
return res.status(409).json({ error: 'Username or email already exists' });
|
||||
}
|
||||
res.status(500).json({ error: 'Failed to update user' });
|
||||
@@ -304,31 +281,23 @@ function createUsersRouter(db, requireAuth, requireGroup, logAudit) {
|
||||
|
||||
try {
|
||||
// Look up the user before deleting
|
||||
const targetUser = await new Promise((resolve, reject) => {
|
||||
db.get('SELECT username FROM users WHERE id = ?', [userId], (err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
});
|
||||
});
|
||||
const { rows: userRows } = await pool.query(
|
||||
'SELECT username FROM users WHERE id = $1',
|
||||
[userId]
|
||||
);
|
||||
const targetUser = userRows[0];
|
||||
|
||||
// Delete sessions first (foreign key)
|
||||
await new Promise((resolve) => {
|
||||
db.run('DELETE FROM sessions WHERE user_id = ?', [userId], () => resolve());
|
||||
});
|
||||
await pool.query('DELETE FROM sessions WHERE user_id = $1', [userId]);
|
||||
|
||||
// Delete user
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
db.run('DELETE FROM users WHERE id = ?', [userId], function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ changes: this.changes });
|
||||
});
|
||||
});
|
||||
const result = await pool.query('DELETE FROM users WHERE id = $1', [userId]);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
action: 'user_delete',
|
||||
|
||||
Reference in New Issue
Block a user