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,54 +6,74 @@ const router = Router();
// GET /api/campaigns
router.get("/", async (_req, res) => {
const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM campaigns ORDER BY created_at DESC"
);
res.json(rows);
try {
const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM campaigns ORDER BY created_at DESC"
);
res.json(rows);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
});
// POST /api/campaigns
router.post("/", async (req, res) => {
const { name } = req.body;
if (!name?.trim()) {
res.status(400).json({ error: "Campaign name is required" });
return;
try {
const { name } = req.body;
if (!name?.trim()) {
res.status(400).json({ error: "Campaign name is required" });
return;
}
const [result] = await db.execute<ResultSetHeader>(
"INSERT INTO campaigns (name) VALUES (?)",
[name.trim()]
);
const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM campaigns WHERE id = ?",
[result.insertId]
);
res.status(201).json(rows[0]);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
const [result] = await db.execute<ResultSetHeader>(
"INSERT INTO campaigns (name) VALUES (?)",
[name.trim()]
);
const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM campaigns WHERE id = ?",
[result.insertId]
);
res.status(201).json(rows[0]);
});
// GET /api/campaigns/:id
router.get("/:id", async (req, res) => {
const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM campaigns WHERE id = ?",
[req.params.id]
);
if (rows.length === 0) {
res.status(404).json({ error: "Campaign not found" });
return;
try {
const [rows] = await db.execute<RowDataPacket[]>(
"SELECT * FROM campaigns WHERE id = ?",
[req.params.id]
);
if (rows.length === 0) {
res.status(404).json({ error: "Campaign not found" });
return;
}
res.json(rows[0]);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
res.json(rows[0]);
});
// DELETE /api/campaigns/:id
router.delete("/:id", async (req, res) => {
const [result] = await db.execute<ResultSetHeader>(
"DELETE FROM campaigns WHERE id = ?",
[req.params.id]
);
if (result.affectedRows === 0) {
res.status(404).json({ error: "Campaign not found" });
return;
try {
const [result] = await db.execute<ResultSetHeader>(
"DELETE FROM campaigns WHERE id = ?",
[req.params.id]
);
if (result.affectedRows === 0) {
res.status(404).json({ error: "Campaign not found" });
return;
}
res.status(204).end();
} catch (err) {
console.error(err);
res.status(500).json({ error: "Internal server error" });
}
res.status(204).end();
});
export default router;

View file

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

View file

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

View file

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