feat: spell catalog and character spell management routes
This commit is contained in:
parent
7ad0f1410d
commit
ff7f22d77b
3 changed files with 101 additions and 0 deletions
|
|
@ -12,6 +12,7 @@ import characterRoutes from "./routes/characters.js";
|
|||
import gameItemRoutes from "./routes/game-items.js";
|
||||
import gameTalentRoutes from "./routes/game-talents.js";
|
||||
import rollRoutes from "./routes/rolls.js";
|
||||
import spellRoutes from "./routes/spells.js";
|
||||
import authRoutes from "./routes/auth.js";
|
||||
import db from "./db.js";
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ app.use("/api/characters", characterRoutes);
|
|||
app.use("/api/game-items", gameItemRoutes);
|
||||
app.use("/api/game-talents", gameTalentRoutes);
|
||||
app.use("/api/campaigns/:campaignId/rolls", rollRoutes);
|
||||
app.use("/api/spells", spellRoutes);
|
||||
|
||||
const PORT = process.env.PORT ?? 3000;
|
||||
|
||||
|
|
|
|||
|
|
@ -437,4 +437,78 @@ router.delete("/:id/talents/:talentId", async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
async function getCharacterCampaignId(id: number): Promise<{ campaign_id: number }> {
|
||||
const [rows] = await db.execute<RowDataPacket[]>("SELECT campaign_id FROM characters WHERE id = ?", [id]);
|
||||
return rows[0] as { campaign_id: number };
|
||||
}
|
||||
|
||||
// GET /api/characters/:id/spells
|
||||
router.get("/:id/spells", requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
const [rows] = await db.execute<RowDataPacket[]>(
|
||||
`SELECT cs.id, cs.spell_id, cs.exhausted, cs.locked_until, cs.focus_active, cs.focus_started_at,
|
||||
s.name, s.class, s.tier, s.casting_stat, s.duration, s.range, s.is_focus, s.description
|
||||
FROM character_spells cs
|
||||
JOIN spells s ON s.id = cs.spell_id
|
||||
WHERE cs.character_id = ?
|
||||
ORDER BY s.tier, s.name`,
|
||||
[req.params.id]
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
// POST /api/characters/:id/spells
|
||||
router.post("/:id/spells", requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
const { spell_id } = req.body;
|
||||
await db.execute(
|
||||
"INSERT IGNORE INTO character_spells (character_id, spell_id) VALUES (?, ?)",
|
||||
[req.params.id, spell_id]
|
||||
);
|
||||
const [rows] = await db.execute<RowDataPacket[]>(
|
||||
`SELECT cs.*, s.name, s.class, s.tier, s.casting_stat, s.duration, s.range, s.is_focus, s.description
|
||||
FROM character_spells cs JOIN spells s ON s.id = cs.spell_id
|
||||
WHERE cs.character_id = ? AND cs.spell_id = ?`,
|
||||
[req.params.id, spell_id]
|
||||
);
|
||||
const io = req.app.get("io");
|
||||
const char = await getCharacterCampaignId(Number(req.params.id));
|
||||
io.to(String(char.campaign_id)).emit("spell:added", { characterId: Number(req.params.id), spell: rows[0] });
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
// DELETE /api/characters/:id/spells/:spellId
|
||||
router.delete("/:id/spells/:spellId", requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
await db.execute(
|
||||
"DELETE FROM character_spells WHERE character_id = ? AND spell_id = ?",
|
||||
[req.params.id, req.params.spellId]
|
||||
);
|
||||
const io = req.app.get("io");
|
||||
const char = await getCharacterCampaignId(Number(req.params.id));
|
||||
io.to(String(char.campaign_id)).emit("spell:removed", { characterId: Number(req.params.id), spellId: Number(req.params.spellId) });
|
||||
res.json({ ok: true });
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
// POST /api/characters/:id/rest
|
||||
router.post("/:id/rest", requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
await db.execute(
|
||||
"UPDATE character_spells SET exhausted = 0, focus_active = 0, focus_started_at = NULL WHERE character_id = ? AND locked_until IS NULL",
|
||||
[req.params.id]
|
||||
);
|
||||
await db.execute(
|
||||
"DELETE FROM character_conditions WHERE character_id = ?",
|
||||
[req.params.id]
|
||||
);
|
||||
const io = req.app.get("io");
|
||||
const char = await getCharacterCampaignId(Number(req.params.id));
|
||||
io.to(String(char.campaign_id)).emit("character:rested", { characterId: Number(req.params.id) });
|
||||
res.json({ ok: true });
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
25
server/src/routes/spells.ts
Normal file
25
server/src/routes/spells.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { Router } from "express";
|
||||
import type { RowDataPacket } from "mysql2";
|
||||
import db from "../db.js";
|
||||
import { requireAuth } from "../auth/middleware.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
// GET /api/spells — all spells, optionally filtered by class
|
||||
router.get("/", requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
const { class: spellClass } = req.query;
|
||||
let sql = "SELECT * FROM spells ORDER BY class, tier, name";
|
||||
const params: string[] = [];
|
||||
if (spellClass && typeof spellClass === "string") {
|
||||
sql = "SELECT * FROM spells WHERE class = ? OR class = 'both' ORDER BY tier, name";
|
||||
params.push(spellClass);
|
||||
}
|
||||
const [rows] = await db.execute<RowDataPacket[]>(sql, params);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Loading…
Add table
Reference in a new issue