Files
gamercomp/backend/src/routes/bugs.js
T
2026-04-30 02:14:25 +00:00

217 lines
6.3 KiB
JavaScript

const express = require('express');
const { body, param, query } = require('express-validator');
const { pool } = require('../models/db');
const { authenticate, requireRole } = require('../middleware/auth');
const validate = require('../middleware/validate');
const Notifications = require('../models/notifications');
const { sendNotificationEmail } = require('../utils/email');
const router = express.Router();
// Submit a bug report (authenticated users)
router.post('/',
authenticate,
validate([
body('title').trim().isLength({ min: 5, max: 200 }).withMessage('Title must be 5-200 characters'),
body('description').trim().isLength({ min: 10, max: 5000 }).withMessage('Description must be 10-5000 characters'),
body('pageUrl').optional().trim().isLength({ max: 500 })
]),
async (req, res, next) => {
try {
const { title, description, pageUrl } = req.body;
const result = await pool.query(
`INSERT INTO bug_reports (user_id, title, description, page_url)
VALUES ($1, $2, $3, $4)
RETURNING *`,
[req.user.id, title, description, pageUrl || null]
);
res.status(201).json({
message: 'Bug report submitted. Thank you for helping improve GamerComp!',
report: result.rows[0]
});
} catch (error) {
next(error);
}
}
);
// Get my bug reports (authenticated users)
router.get('/my-reports',
authenticate,
async (req, res, next) => {
try {
const result = await pool.query(
`SELECT id, title, status, priority, created_at, resolved_at
FROM bug_reports
WHERE user_id = $1
ORDER BY created_at DESC`,
[req.user.id]
);
res.json({ reports: result.rows });
} catch (error) {
next(error);
}
}
);
// Admin: Get all bug reports
router.get('/admin',
authenticate,
requireRole('admin'),
async (req, res, next) => {
try {
const { status, priority } = req.query;
let query = `
SELECT br.*,
u.username as reporter_username,
u.email as reporter_email,
resolver.username as resolver_username
FROM bug_reports br
LEFT JOIN users u ON br.user_id = u.id
LEFT JOIN users resolver ON br.resolved_by = resolver.id
WHERE 1=1
`;
const params = [];
if (status && status !== 'all') {
params.push(status);
query += ` AND br.status = $${params.length}`;
}
if (priority && priority !== 'all') {
params.push(priority);
query += ` AND br.priority = $${params.length}`;
}
query += ` ORDER BY
CASE br.priority
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'normal' THEN 3
WHEN 'low' THEN 4
END,
br.created_at DESC`;
const result = await pool.query(query, params);
// Get counts by status
const countsResult = await pool.query(`
SELECT status, COUNT(*) as count
FROM bug_reports
GROUP BY status
`);
const counts = {};
countsResult.rows.forEach(row => {
counts[row.status] = parseInt(row.count);
});
res.json({
reports: result.rows,
counts
});
} catch (error) {
next(error);
}
}
);
// Admin: Update bug report status
router.put('/admin/:id',
authenticate,
requireRole('admin'),
validate([
param('id').isInt(),
body('status').isIn(['open', 'in_progress', 'resolved', 'closed', 'wont_fix']),
body('priority').optional().isIn(['low', 'normal', 'high', 'critical']),
body('adminNotes').optional().trim().isLength({ max: 2000 })
]),
async (req, res, next) => {
try {
const { id } = req.params;
const { status, priority, adminNotes } = req.body;
// Get the current bug report
const current = await pool.query(
'SELECT * FROM bug_reports WHERE id = $1',
[id]
);
if (current.rows.length === 0) {
return res.status(404).json({ error: 'Bug report not found' });
}
const bug = current.rows[0];
const wasResolved = ['resolved', 'closed'].includes(bug.status);
const isNowResolved = ['resolved', 'closed'].includes(status);
// Update the bug report
const isResolvingNow = ['resolved', 'closed'].includes(status);
const result = await pool.query(
`UPDATE bug_reports
SET status = $2,
priority = COALESCE($3, priority),
admin_notes = COALESCE($4, admin_notes),
resolved_at = CASE WHEN $5 AND resolved_at IS NULL THEN NOW() ELSE resolved_at END,
resolved_by = CASE WHEN $5 AND resolved_by IS NULL THEN $6 ELSE resolved_by END,
updated_at = NOW()
WHERE id = $1
RETURNING *`,
[id, status, priority || null, adminNotes || null, isResolvingNow, req.user.id]
);
// If status changed to resolved/closed and user exists, notify them
if (!wasResolved && isNowResolved && bug.user_id) {
const statusText = status === 'resolved' ? 'has been fixed' : 'has been closed';
const notifMessage = `Your bug report "${bug.title}" ${statusText}. Thank you for helping improve GamerComp!`;
await Notifications.create({
userId: bug.user_id,
type: 'bug_resolved',
title: 'Bug Report Updated',
message: notifMessage,
link: null
});
// Send email notification
const userResult = await pool.query('SELECT email FROM users WHERE id = $1', [bug.user_id]);
if (userResult.rows.length > 0 && userResult.rows[0].email) {
sendNotificationEmail(
{ email: userResult.rows[0].email },
{ title: 'Bug Report Updated', message: notifMessage }
).catch(() => {});
}
}
res.json({
message: 'Bug report updated',
report: result.rows[0]
});
} catch (error) {
next(error);
}
}
);
// Admin: Delete bug report
router.delete('/admin/:id',
authenticate,
requireRole('admin'),
validate([param('id').isInt()]),
async (req, res, next) => {
try {
const { id } = req.params;
await pool.query('DELETE FROM bug_reports WHERE id = $1', [id]);
res.json({ message: 'Bug report deleted' });
} catch (error) {
next(error);
}
}
);
module.exports = router;