Files

140 lines
5.9 KiB
JavaScript

const express = require('express');
const { errorReportHelpers, logToFile, pool } = require('../mariadb');
const { validateSession } = require('../sessionModel');
const { exec } = require('child_process');
const execPromise = (cmd) => new Promise((resolve, reject) => exec(cmd, { timeout: 30000 }, (err, stdout) => err ? reject(err) : resolve(stdout)));
const router = express.Router();
// Middleware: require valid session (logged-in user)
const requireSession = async (req, res, next) => {
const sessionId = req.headers['x-session-id'] || (req.body && req.body.sessionId) || (req.query && req.query.sessionId);
if (!sessionId) return res.status(401).json({ error: 'Login required' });
const session = await validateSession(sessionId);
if (!session || !session.data || !session.data.playerName) {
return res.status(401).json({ error: 'Invalid or expired session' });
}
req.session = session;
req.sessionPlayerName = session.data.playerName;
next();
};
// POST /api/errors - Submit error report (requires login)
router.post('/', requireSession, async (req, res) => {
try {
const { title, description, category, pageUrl } = req.body;
if (!title || !description) {
return res.status(400).json({ error: 'Title and description are required' });
}
const reportId = await errorReportHelpers.create({
playerName: req.sessionPlayerName,
title,
description,
category: category || 'other',
pageUrl: pageUrl || ''
});
logToFile(`API: Error report #${reportId} created by ${req.sessionPlayerName}`);
// Also create a GitHub issue
let issueUrl = null;
try {
const body = [
`**Category:** ${category || 'other'}\n`,
`**Reported by:** ${req.sessionPlayerName}\n`,
`**Page:** ${pageUrl || 'N/A'}\n`,
`**Description:**\n${description}`
].join('\n');
const label = category && category !== 'other' ? category : 'other';
const result = await execPromise(`cd /nas/git/dwroller && gh issue create --title "Error: ${title}" --body '${body.replace(/'/g, "\\'")}' --label '${label}' --repo alexpolo1/dwroller`);
// gh issue create outputs: https://github.com/...#issue-xxx
issueUrl = result.trim();
logToFile(`API: GitHub issue created: ${issueUrl}`);
// Update DB with the GitHub issue URL
await pool.execute('UPDATE error_reports SET github_issue_url = ? WHERE id = ?', [issueUrl, reportId]);
} catch (ghError) {
logToFile('API: Failed to create GitHub issue', ghError);
// Don't fail the request — the DB report still succeeded
}
res.json({ success: true, id: reportId, githubIssueUrl: issueUrl });
} catch (error) {
logToFile('API: Error creating report', error);
res.status(500).json({ error: String(error) });
}
});
// GET /api/errors - List error reports (requires login, GM sees all, player sees their own)
router.get('/', requireSession, async (req, res) => {
try {
const isGM = req.sessionPlayerName && req.sessionPlayerName.toLowerCase() === 'gm';
const reports = isGM
? await errorReportHelpers.getAll()
: await errorReportHelpers.getByPlayer(req.sessionPlayerName);
res.json(reports);
} catch (error) {
logToFile('API: Error listing reports', error);
res.status(500).json({ error: String(error) });
}
});
// GET /api/errors/:id - Get one error report (requires login)
router.get('/:id', requireSession, async (req, res) => {
try {
const report = await errorReportHelpers.getById(parseInt(req.params.id));
if (!report) return res.status(404).json({ error: 'Not found' });
// Players can only see their own reports, GM sees all
const isGM = req.sessionPlayerName && req.sessionPlayerName.toLowerCase() === 'gm';
if (!isGM && report.player_name !== req.sessionPlayerName) {
return res.status(403).json({ error: 'Access denied' });
}
res.json(report);
} catch (error) {
logToFile('API: Error getting report', error);
res.status(500).json({ error: String(error) });
}
});
// PUT /api/errors/:id/resolve - Resolve error report (GM only)
router.put('/:id/resolve', requireSession, async (req, res) => {
try {
const isGM = req.sessionPlayerName && req.sessionPlayerName.toLowerCase() === 'gm';
if (!isGM) return res.status(403).json({ error: 'GM access required' });
const { note } = req.body;
await errorReportHelpers.resolve(parseInt(req.params.id), req.sessionPlayerName, note || '');
logToFile(`API: Error report #${req.params.id} resolved by ${req.sessionPlayerName}`);
res.json({ success: true });
} catch (error) {
logToFile('API: Error resolving report', error);
res.status(500).json({ error: String(error) });
}
});
// PUT /api/errors/:id/status - Update status (GM only)
router.put('/:id/status', requireSession, async (req, res) => {
try {
const isGM = req.sessionPlayerName && req.sessionPlayerName.toLowerCase() === 'gm';
if (!isGM) return res.status(403).json({ error: 'GM access required' });
const { status } = req.body;
await errorReportHelpers.updateStatus(parseInt(req.params.id), status);
res.json({ success: true });
} catch (error) {
logToFile('API: Error updating status', error);
res.status(500).json({ error: String(error) });
}
});
// DELETE /api/errors/:id - Delete error report (GM only)
router.delete('/:id', requireSession, async (req, res) => {
try {
const isGM = req.sessionPlayerName && req.sessionPlayerName.toLowerCase() === 'gm';
if (!isGM) return res.status(403).json({ error: 'GM access required' });
await errorReportHelpers.delete(parseInt(req.params.id));
logToFile(`API: Error report #${req.params.id} deleted by ${req.sessionPlayerName}`);
res.json({ success: true });
} catch (error) {
logToFile('API: Error deleting report', error);
res.status(500).json({ error: String(error) });
}
});
console.log('Error report routes registered');
module.exports = router;