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:
Jordan Ramos
2026-05-06 11:44:17 -06:00
parent 845d843e71
commit 33927b150b
18 changed files with 2164 additions and 4432 deletions

View File

@@ -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',