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;