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,5 +1,6 @@
// routes/archerTickets.js
const express = require('express');
const pool = require('../db');
const { requireAuth, requireGroup } = require('../middleware/auth');
const logAudit = require('../helpers/auditLog');
@@ -13,42 +14,43 @@ function isValidVendor(vendor) {
return typeof vendor === 'string' && vendor.trim().length > 0 && vendor.length <= 200;
}
function createArcherTicketsRouter(db) {
function createArcherTicketsRouter() {
const router = express.Router();
// Get all Archer tickets (with optional filters)
router.get('/', requireAuth(db), (req, res) => {
router.get('/', requireAuth(), async (req, res) => {
const { cve_id, vendor, status } = req.query;
let query = 'SELECT * FROM archer_tickets WHERE 1=1';
const params = [];
let paramIndex = 1;
if (cve_id) {
query += ' AND cve_id = ?';
query += ` AND cve_id = $${paramIndex++}`;
params.push(cve_id);
}
if (vendor) {
query += ' AND vendor = ?';
query += ` AND vendor = $${paramIndex++}`;
params.push(vendor);
}
if (status) {
query += ' AND status = ?';
query += ` AND status = $${paramIndex++}`;
params.push(status);
}
query += ' ORDER BY created_at DESC';
db.all(query, params, (err, rows) => {
if (err) {
console.error('Error fetching Archer tickets:', err);
return res.status(500).json({ error: 'Internal server error.' });
}
try {
const { rows } = await pool.query(query, params);
res.json(rows);
});
} catch (err) {
console.error('Error fetching Archer tickets:', err);
res.status(500).json({ error: 'Internal server error.' });
}
});
// Create Archer ticket
router.post('/', requireAuth(db), requireGroup('Admin', 'Standard_User'), (req, res) => {
router.post('/', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
const { exc_number, archer_url, status, cve_id, vendor } = req.body;
// Validation
@@ -73,38 +75,38 @@ function createArcherTicketsRouter(db) {
const validatedStatus = status || 'Draft';
db.run(
`INSERT INTO archer_tickets (exc_number, archer_url, status, cve_id, vendor, created_by)
VALUES (?, ?, ?, ?, ?, ?)`,
[exc_number.trim(), archer_url || null, validatedStatus, cve_id, vendor, req.user.id],
function(err) {
if (err) {
console.error('Error creating Archer ticket:', err);
if (err.message.includes('UNIQUE constraint failed')) {
return res.status(409).json({ error: 'An Archer ticket with this EXC number already exists.' });
}
return res.status(500).json({ error: 'Internal server error.' });
}
try {
const { rows } = await pool.query(
`INSERT INTO archer_tickets (exc_number, archer_url, status, cve_id, vendor, created_by)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id`,
[exc_number.trim(), archer_url || null, validatedStatus, cve_id, vendor, req.user.id]
);
logAudit(db, {
userId: req.user.id,
action: 'CREATE_ARCHER_TICKET',
entityType: 'archer_ticket',
entityId: String(this.lastID),
details: { exc_number, archer_url, status: validatedStatus, cve_id, vendor },
ipAddress: req.ip
});
logAudit({
userId: req.user.id,
action: 'CREATE_ARCHER_TICKET',
entityType: 'archer_ticket',
entityId: String(rows[0].id),
details: { exc_number, archer_url, status: validatedStatus, cve_id, vendor },
ipAddress: req.ip
});
res.status(201).json({
id: this.lastID,
message: 'Archer ticket created successfully'
});
res.status(201).json({
id: rows[0].id,
message: 'Archer ticket created successfully'
});
} catch (err) {
console.error('Error creating Archer ticket:', err);
if (err.code === '23505') {
return res.status(409).json({ error: 'An Archer ticket with this EXC number already exists.' });
}
);
res.status(500).json({ error: 'Internal server error.' });
}
});
// Update Archer ticket
router.put('/:id', requireAuth(db), requireGroup('Admin', 'Standard_User'), (req, res) => {
router.put('/:id', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
const { id } = req.params;
const { exc_number, archer_url, status } = req.body;
@@ -124,29 +126,27 @@ function createArcherTicketsRouter(db) {
return res.status(400).json({ error: 'Invalid status. Must be Draft, Open, Under Review, or Accepted.' });
}
// Get existing ticket
db.get('SELECT * FROM archer_tickets WHERE id = ?', [id], (err, existing) => {
if (err) {
console.error(err);
return res.status(500).json({ error: 'Internal server error.' });
}
try {
const { rows } = await pool.query('SELECT * FROM archer_tickets WHERE id = $1', [id]);
const existing = rows[0];
if (!existing) {
return res.status(404).json({ error: 'Archer ticket not found.' });
}
const updates = [];
const params = [];
let paramIndex = 1;
if (exc_number !== undefined) {
updates.push('exc_number = ?');
updates.push(`exc_number = $${paramIndex++}`);
params.push(exc_number.trim());
}
if (archer_url !== undefined) {
updates.push('archer_url = ?');
updates.push(`archer_url = $${paramIndex++}`);
params.push(archer_url || null);
}
if (status !== undefined) {
updates.push('status = ?');
updates.push(`status = $${paramIndex++}`);
params.push(status);
}
@@ -154,73 +154,47 @@ function createArcherTicketsRouter(db) {
return res.status(400).json({ error: 'No fields to update.' });
}
updates.push('updated_at = CURRENT_TIMESTAMP');
updates.push('updated_at = NOW()');
params.push(id);
db.run(
`UPDATE archer_tickets SET ${updates.join(', ')} WHERE id = ?`,
params,
function(err) {
if (err) {
console.error(err);
if (err.message.includes('UNIQUE constraint failed')) {
return res.status(409).json({ error: 'An Archer ticket with this EXC number already exists.' });
}
return res.status(500).json({ error: 'Internal server error.' });
}
logAudit(db, {
userId: req.user.id,
action: 'UPDATE_ARCHER_TICKET',
entityType: 'archer_ticket',
entityId: String(id),
details: { before: existing, changes: req.body },
ipAddress: req.ip
});
res.json({ message: 'Archer ticket updated successfully', changes: this.changes });
}
const result = await pool.query(
`UPDATE archer_tickets SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
params
);
});
});
// Helper: perform the actual Archer ticket deletion
function performArcherDelete(db, req, res, id, ticket) {
db.run('DELETE FROM archer_tickets WHERE id = ?', [id], function(err) {
if (err) {
console.error(err);
return res.status(500).json({ error: 'Internal server error.' });
}
logAudit(db, {
logAudit({
userId: req.user.id,
action: 'DELETE_ARCHER_TICKET',
action: 'UPDATE_ARCHER_TICKET',
entityType: 'archer_ticket',
entityId: String(id),
details: { deleted: ticket },
details: { before: existing, changes: req.body },
ipAddress: req.ip
});
res.json({ message: 'Archer ticket deleted successfully' });
});
}
res.json({ message: 'Archer ticket updated successfully', changes: result.rowCount });
} catch (err) {
console.error(err);
if (err.code === '23505') {
return res.status(409).json({ error: 'An Archer ticket with this EXC number already exists.' });
}
res.status(500).json({ error: 'Internal server error.' });
}
});
// Delete Archer ticket
router.delete('/:id', requireAuth(db), requireGroup('Admin', 'Standard_User'), (req, res) => {
router.delete('/:id', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
const { id } = req.params;
db.get('SELECT * FROM archer_tickets WHERE id = ?', [id], (err, ticket) => {
if (err) {
console.error(err);
return res.status(500).json({ error: 'Internal server error.' });
}
try {
const { rows } = await pool.query('SELECT * FROM archer_tickets WHERE id = $1', [id]);
const ticket = rows[0];
if (!ticket) {
return res.status(404).json({ error: 'Archer ticket not found.' });
}
// Admin bypasses all delete restrictions
if (req.user.group === 'Admin') {
return performArcherDelete(db, req, res, id, ticket);
return performArcherDelete();
}
// Standard_User: ownership check
@@ -230,53 +204,63 @@ function createArcherTicketsRouter(db) {
// Standard_User: compliance linkage check
const excNumber = ticket.exc_number;
db.all(
`SELECT ci.id, ci.extra_json
FROM compliance_items ci
JOIN compliance_uploads cu ON ci.upload_id = cu.id
WHERE ci.status = 'active' AND ci.extra_json LIKE ?`,
[`%${excNumber}%`],
(compErr, compLinks) => {
// If compliance_items table doesn't exist yet, treat as no linkage
if (compErr && compErr.message && compErr.message.includes('no such table')) {
compLinks = [];
} else if (compErr) {
console.error(compErr);
return res.status(500).json({ error: 'Internal server error.' });
}
try {
const { rows: compLinks } = await pool.query(
`SELECT ci.id, ci.extra_json
FROM compliance_items ci
JOIN compliance_uploads cu ON ci.upload_id = cu.id
WHERE ci.status = 'active' AND ci.extra_json ILIKE $1`,
[`%${excNumber}%`]
);
const isLinked = (compLinks || []).some(cl => {
const json = cl.extra_json || '';
return json.includes(excNumber);
});
const isLinked = (compLinks || []).some(cl => {
const json = cl.extra_json || '';
return json.includes(excNumber);
});
if (isLinked) {
return res.status(403).json({ error: 'Cannot delete ticket linked to compliance report. Contact an admin.' });
}
return performArcherDelete(db, req, res, id, ticket);
if (isLinked) {
return res.status(403).json({ error: 'Cannot delete ticket linked to compliance report. Contact an admin.' });
}
);
});
} catch (compErr) {
if (!compErr.message.includes('does not exist')) throw compErr;
}
return performArcherDelete();
async function performArcherDelete() {
await pool.query('DELETE FROM archer_tickets WHERE id = $1', [id]);
logAudit({
userId: req.user.id,
action: 'DELETE_ARCHER_TICKET',
entityType: 'archer_ticket',
entityId: String(id),
details: { deleted: ticket },
ipAddress: req.ip
});
res.json({ message: 'Archer ticket deleted successfully' });
}
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error.' });
}
});
// GET /status-trend — ticket counts grouped by creation date + status
// Used for time-based Archer pipeline chart on the Compliance page.
router.get('/status-trend', requireAuth(db), (req, res) => {
db.all(
`SELECT DATE(created_at) AS date, status, COUNT(*) AS count
FROM archer_tickets
GROUP BY DATE(created_at), status
ORDER BY date ASC`,
[],
(err, rows) => {
if (err) {
console.error('Error fetching Archer status trend:', err);
return res.status(500).json({ error: 'Internal server error.' });
}
res.json({ statusTrend: rows });
}
);
router.get('/status-trend', requireAuth(), async (req, res) => {
try {
const { rows } = await pool.query(
`SELECT DATE(created_at) AS date, status, COUNT(*) AS count
FROM archer_tickets
GROUP BY DATE(created_at), status
ORDER BY date ASC`
);
res.json({ statusTrend: rows });
} catch (err) {
console.error('Error fetching Archer status trend:', err);
res.status(500).json({ error: 'Internal server error.' });
}
});
return router;