217 lines
6.3 KiB
JavaScript
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;
|