fix: add error handling and safe JSON parsing to route handlers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron Wood 2026-04-11 00:12:08 -04:00
parent 268997a009
commit 2812d81979
5 changed files with 118 additions and 64 deletions

View file

@ -6,14 +6,20 @@ const router = Router();
// GET /api/campaigns // GET /api/campaigns
router.get("/", async (_req, res) => { router.get("/", async (_req, res) => {
try {
const [rows] = await db.execute<RowDataPacket[]>( const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM campaigns ORDER BY created_at DESC" "SELECT * FROM campaigns ORDER BY created_at DESC"
); );
res.json(rows); res.json(rows);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
}); });
// POST /api/campaigns // POST /api/campaigns
router.post("/", async (req, res) => { router.post("/", async (req, res) => {
try {
const { name } = req.body; const { name } = req.body;
if (!name?.trim()) { if (!name?.trim()) {
res.status(400).json({ error: "Campaign name is required" }); res.status(400).json({ error: "Campaign name is required" });
@ -28,10 +34,15 @@ router.post("/", async (req, res) => {
[result.insertId] [result.insertId]
); );
res.status(201).json(rows[0]); res.status(201).json(rows[0]);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
}); });
// GET /api/campaigns/:id // GET /api/campaigns/:id
router.get("/:id", async (req, res) => { router.get("/:id", async (req, res) => {
try {
const [rows] = await db.execute<RowDataPacket[]>( const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM campaigns WHERE id = ?", "SELECT * FROM campaigns WHERE id = ?",
[req.params.id] [req.params.id]
@ -41,10 +52,15 @@ router.get("/:id", async (req, res) => {
return; return;
} }
res.json(rows[0]); res.json(rows[0]);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
}); });
// DELETE /api/campaigns/:id // DELETE /api/campaigns/:id
router.delete("/:id", async (req, res) => { router.delete("/:id", async (req, res) => {
try {
const [result] = await db.execute<ResultSetHeader>( const [result] = await db.execute<ResultSetHeader>(
"DELETE FROM campaigns WHERE id = ?", "DELETE FROM campaigns WHERE id = ?",
[req.params.id] [req.params.id]
@ -54,6 +70,10 @@ router.delete("/:id", async (req, res) => {
return; return;
} }
res.status(204).end(); res.status(204).end();
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
}); });
export default router; export default router;

View file

@ -1,19 +1,26 @@
import { Router } from "express"; import { Router } from "express";
import type { RowDataPacket } from "mysql2"; import type { RowDataPacket } from "mysql2";
import db from "../db.js"; import db from "../db.js";
import { parseJson } from "../utils/parseJson.js";
const router = Router(); const router = Router();
// GET /api/game-items
router.get("/", async (_req, res) => { router.get("/", async (_req, res) => {
try {
const [rows] = await db.execute<RowDataPacket[]>( const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM game_items ORDER BY type, name" "SELECT * FROM game_items ORDER BY type, name"
); );
const parsed = rows.map((item) => ({ const parsed = rows.map((item) => ({
...item, ...item,
effects: JSON.parse(item.effects as string), effects: parseJson(item.effects),
properties: JSON.parse(item.properties as string), properties: parseJson(item.properties),
})); }));
res.json(parsed); res.json(parsed);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
}); });
export default router; export default router;

View file

@ -1,18 +1,25 @@
import { Router } from "express"; import { Router } from "express";
import type { RowDataPacket } from "mysql2"; import type { RowDataPacket } from "mysql2";
import db from "../db.js"; import db from "../db.js";
import { parseJson } from "../utils/parseJson.js";
const router = Router(); const router = Router();
// GET /api/game-talents
router.get("/", async (_req, res) => { router.get("/", async (_req, res) => {
try {
const [rows] = await db.execute<RowDataPacket[]>( const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM game_talents ORDER BY source, name" "SELECT * FROM game_talents ORDER BY source, name"
); );
const parsed = rows.map((t) => ({ const parsed = rows.map((t) => ({
...t, ...t,
effect: JSON.parse(t.effect as string), effect: parseJson(t.effect),
})); }));
res.json(parsed); res.json(parsed);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
}); });
export default router; export default router;

View file

@ -1,23 +1,33 @@
import { Router } from "express"; import { Router } from "express";
import type { ParamsDictionary } from "express-serve-static-core";
import type { RowDataPacket } from "mysql2"; import type { RowDataPacket } from "mysql2";
import db from "../db.js"; import db from "../db.js";
import { parseJson } from "../utils/parseJson.js";
type CampaignParams = ParamsDictionary & { campaignId: string };
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true });
router.get("/", async (req: any, res) => { // GET /api/campaigns/:campaignId/rolls
const { campaignId } = req.params as { campaignId: string }; router.get<CampaignParams>("/", async (req, res) => {
try {
const { campaignId } = req.params;
const [rows] = await db.execute<RowDataPacket[]>( const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM roll_log WHERE campaign_id = ? ORDER BY created_at DESC LIMIT 50", "SELECT * FROM roll_log WHERE campaign_id = ? ORDER BY created_at DESC LIMIT 50",
[campaignId] [campaignId]
); );
const parsed = rows.map((r) => ({ const parsed = rows.map((r) => ({
...r, ...r,
rolls: JSON.parse(r.rolls as string), rolls: (parseJson(r.rolls) as unknown) as unknown[],
advantage: r.advantage === 1, advantage: r.advantage === 1,
disadvantage: r.disadvantage === 1, disadvantage: r.disadvantage === 1,
nat20: r.nat20 === 1, nat20: r.nat20 === 1,
})); }));
res.json(parsed); res.json(parsed);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
}); });
export default router; export default router;

View file

@ -0,0 +1,10 @@
export function parseJson(val: unknown): Record<string, unknown> {
if (typeof val === "string") {
try {
return JSON.parse(val);
} catch {
return {};
}
}
return (val as Record<string, unknown>) ?? {};
}