Skip to content

Commit 983fee4

Browse files
Upgrade Express 4->5 and @types/express to match
- Rename wildcard SPA route /* -> /*splat (Express 5 requires named wildcards) - Type req.params with explicit generics on routes using :id/:type/:user/etc, since Express 5's path-to-regexp-based typing widens params to string | string[] by default Verified: build, full test suite (310 passing), and a live server smoke test of both an API route and the SPA wildcard fallback route. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 43fbb36 commit 983fee4

8 files changed

Lines changed: 362 additions & 332 deletions

File tree

package-lock.json

Lines changed: 293 additions & 274 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"discord.js": "^14.26.4",
3131
"dompurify": "^3.4.11",
3232
"dotenv": "^17.4.2",
33-
"express": "^4.19.2",
33+
"express": "^5.2.1",
3434
"firebase": "^10.14.1",
3535
"firebase-admin": "^14.0.0",
3636
"firebaseui": "^6.1.0",
@@ -70,7 +70,7 @@
7070
"@testing-library/user-event": "^14.6.1",
7171
"@types/bcrypt": "^6.0.0",
7272
"@types/body-parser": "^1.19.6",
73-
"@types/express": "^4.17.21",
73+
"@types/express": "^5.0.6",
7474
"@types/jest": "^30.0.0",
7575
"@types/jwt-simple": "^0.5.36",
7676
"@types/lodash": "^4.17.24",

src/controllers/api/telegram.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { checkUser } from "./users";
55

66
export const telegramRoute = Router();
77

8-
telegramRoute.get("/:id/games", async (req: Request, res: Response) => {
8+
telegramRoute.get("/:id/games", async (req: Request<{ id: string }>, res: Response) => {
99
if (checkUser(req.params.id, res)) {
1010
const user = await User.findOne({ _id: res.locals.user._id });
1111
if (!user?.telegramId) res.sendStatus(400);
@@ -17,7 +17,7 @@ telegramRoute.get("/:id/games", async (req: Request, res: Response) => {
1717
}
1818
});
1919

20-
telegramRoute.post("/:id/link", async (req: Request, res: Response) => {
20+
telegramRoute.post("/:id/link", async (req: Request<{ id: string }>, res: Response) => {
2121
if (checkUser(req.params.id, res)) {
2222
const data = req.body;
2323
if (verifyTelegramUser(data)) {

src/controllers/api/tenthings/games.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ tenthingsGamesRoute.get("/mine", async (_: Request, res: Response) => {
4242
return res.json(games);
4343
});
4444

45-
tenthingsGamesRoute.get("/:id", async (req: Request, res: Response) => {
45+
tenthingsGamesRoute.get("/:id", async (req: Request<{ id: string }>, res: Response) => {
4646
const user = res.locals.user;
4747
if (!user) return res.sendStatus(401);
4848
const game = await findGame(req.params.id);
@@ -55,16 +55,19 @@ tenthingsGamesRoute.get("/:id", async (req: Request, res: Response) => {
5555
return res.json({ ...game.toObject(), players });
5656
});
5757

58-
tenthingsGamesRoute.post("/:id/category/:category", async (req: Request, res: Response) => {
59-
if (!res.locals.isAdmin) return res.sendStatus(401);
60-
const game = await findGame(req.params.id);
61-
if (!game) return res.sendStatus(404);
62-
setDisabledCategories(game, req.params.category);
63-
const updatedGame = await game.save();
64-
return res.json(updatedGame.disabledCategories);
65-
});
58+
tenthingsGamesRoute.post(
59+
"/:id/category/:category",
60+
async (req: Request<{ id: string; category: string }>, res: Response) => {
61+
if (!res.locals.isAdmin) return res.sendStatus(401);
62+
const game = await findGame(req.params.id);
63+
if (!game) return res.sendStatus(404);
64+
setDisabledCategories(game, req.params.category);
65+
const updatedGame = await game.save();
66+
return res.json(updatedGame.disabledCategories);
67+
},
68+
);
6669

67-
tenthingsGamesRoute.put("/:id/settings", async (req: Request, res: Response) => {
70+
tenthingsGamesRoute.put("/:id/settings", async (req: Request<{ id: string }>, res: Response) => {
6871
if (!res.locals.isAdmin) return res.sendStatus(401);
6972
const game = await findGame(req.params.id);
7073
if (!game) return res.sendStatus(404);

src/controllers/api/tenthings/lists.ts

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ tenthingsListsRoute.post("/random", async (_: Request, res: Response) => {
111111
}
112112
});
113113

114-
tenthingsListsRoute.get("/:id", async (req: Request, res: Response) => {
114+
tenthingsListsRoute.get("/:id", async (req: Request<{ id: string }>, res: Response) => {
115115
const authorized = !!res.locals.isAuthorized;
116116
if (req.params.id === "names") {
117117
if (!authorized) return res.sendStatus(401);
@@ -128,7 +128,7 @@ tenthingsListsRoute.get("/:id", async (req: Request, res: Response) => {
128128
}
129129
});
130130

131-
tenthingsListsRoute.post("/:id/blurbs/:type", async (req: Request, res: Response) => {
131+
tenthingsListsRoute.post("/:id/blurbs/:type", async (req: Request<{ id: string; type: string }>, res: Response) => {
132132
if (!res.locals.isAuthorized) res.sendStatus(401);
133133
else {
134134
let list = await List.findOne({ _id: req.params.id });
@@ -191,7 +191,7 @@ tenthingsListsRoute.post("/:id/blurbs/:type", async (req: Request, res: Response
191191
}
192192
});
193193

194-
tenthingsListsRoute.get("/:id/report/:user", async (req: Request, res: Response) => {
194+
tenthingsListsRoute.get("/:id/report/:user", async (req: Request<{ id: string; user: string }>, res: Response) => {
195195
if (!res.locals.isAuthorized) res.sendStatus(401);
196196
else {
197197
const list = await List.findOne({ _id: req.params.id });
@@ -204,7 +204,7 @@ tenthingsListsRoute.get("/:id/report/:user", async (req: Request, res: Response)
204204
}
205205
});
206206

207-
tenthingsListsRoute.put("/:id", async (req: Request, res: Response) => {
207+
tenthingsListsRoute.put("/:id", async (req: Request<{ id: string }>, res: Response) => {
208208
if (!res.locals.isAuthorized) res.sendStatus(401);
209209
else {
210210
const yesterday = moment().subtract(1, "days");
@@ -308,7 +308,7 @@ tenthingsListsRoute.post("/merge", async (req: Request, res: Response) => {
308308
}
309309
});
310310

311-
tenthingsListsRoute.delete("/:id", async (req: Request, res: Response) => {
311+
tenthingsListsRoute.delete("/:id", async (req: Request<{ id: string }>, res: Response) => {
312312
if (!res.locals.isAuthorized) res.sendStatus(401);
313313
else {
314314
const list = await List.findOne({ _id: req.params.id });
@@ -336,7 +336,7 @@ tenthingsListsRoute.delete("/:id", async (req: Request, res: Response) => {
336336
}
337337
});
338338

339-
tenthingsListsRoute.post("/:id/values", async (req: Request, res: Response) => {
339+
tenthingsListsRoute.post("/:id/values", async (req: Request<{ id: string }>, res: Response) => {
340340
if (!res.locals.isAuthorized) res.sendStatus(401);
341341
else {
342342
const list = await List.findOne({ _id: req.params.id });
@@ -359,47 +359,55 @@ tenthingsListsRoute.post("/:id/values", async (req: Request, res: Response) => {
359359
}
360360
});
361361

362-
tenthingsListsRoute.put("/:id/values/:valueId", async (req: Request, res: Response) => {
363-
if (!res.locals.isAuthorized) res.sendStatus(401);
364-
else {
365-
const list = await List.findOne({ _id: req.params.id });
366-
if (!list) res.sendStatus(404);
362+
tenthingsListsRoute.put(
363+
"/:id/values/:valueId",
364+
async (req: Request<{ id: string; valueId: string }>, res: Response) => {
365+
if (!res.locals.isAuthorized) res.sendStatus(401);
367366
else {
368-
const value = list.values.find(({ _id }) => _id!.toString() === req.params.valueId);
369-
if (!value) res.sendStatus(404);
370-
else if (value.creator !== res.locals.user?._id && !res.locals.isAdmin) res.sendStatus(401);
367+
const list = await List.findOne({ _id: req.params.id });
368+
if (!list) res.sendStatus(404);
371369
else {
372-
if (moment(list.modifyDate).diff(list.date, "days") > 1) {
373-
bot.notifyAdmins(`<u>Value changed in "${list.name}"</u>\n<b>${value.value}</b> -> <b>${req.body.value}</b>`);
370+
const value = list.values.find(({ _id }) => _id!.toString() === req.params.valueId);
371+
if (!value) res.sendStatus(404);
372+
else if (value.creator !== res.locals.user?._id && !res.locals.isAdmin) res.sendStatus(401);
373+
else {
374+
if (moment(list.modifyDate).diff(list.date, "days") > 1) {
375+
bot.notifyAdmins(
376+
`<u>Value changed in "${list.name}"</u>\n<b>${value.value}</b> -> <b>${req.body.value}</b>`,
377+
);
378+
}
379+
Object.assign(value, req.body);
380+
value.modifyDate = new Date();
381+
list.modifyDate = new Date();
382+
await list.save();
383+
const updatedList = await List.findOne({ _id: req.params.id }).lean({ virtuals: true });
384+
if (!updatedList) res.sendStatus(500);
385+
else res.json(updatedList.values.find(({ _id }) => _id!.toString() === req.params.valueId));
374386
}
375-
Object.assign(value, req.body);
376-
value.modifyDate = new Date();
377-
list.modifyDate = new Date();
378-
await list.save();
379-
const updatedList = await List.findOne({ _id: req.params.id }).lean({ virtuals: true });
380-
if (!updatedList) res.sendStatus(500);
381-
else res.json(updatedList.values.find(({ _id }) => _id!.toString() === req.params.valueId));
382387
}
383388
}
384-
}
385-
});
389+
},
390+
);
386391

387-
tenthingsListsRoute.delete("/:id/values/:valueId", async (req: Request, res: Response) => {
388-
if (!res.locals.isAuthorized) res.sendStatus(401);
389-
else {
390-
const list = await List.findOne({ _id: req.params.id });
391-
if (!list) res.sendStatus(404);
392+
tenthingsListsRoute.delete(
393+
"/:id/values/:valueId",
394+
async (req: Request<{ id: string; valueId: string }>, res: Response) => {
395+
if (!res.locals.isAuthorized) res.sendStatus(401);
392396
else {
393-
const value = list.values.find(({ _id }) => _id!.toString() === req.params.valueId);
394-
if (!value) res.sendStatus(404);
395-
else if (value.creator !== res.locals.user?._id && !res.locals.isAdmin) res.sendStatus(401);
397+
const list = await List.findOne({ _id: req.params.id });
398+
if (!list) res.sendStatus(404);
396399
else {
397-
await List.findByIdAndUpdate(req.params.id, { $pull: { values: { _id: req.params.valueId } } }).exec();
398-
res.sendStatus(200);
400+
const value = list.values.find(({ _id }) => _id!.toString() === req.params.valueId);
401+
if (!value) res.sendStatus(404);
402+
else if (value.creator !== res.locals.user?._id && !res.locals.isAdmin) res.sendStatus(401);
403+
else {
404+
await List.findByIdAndUpdate(req.params.id, { $pull: { values: { _id: req.params.valueId } } }).exec();
405+
res.sendStatus(200);
406+
}
399407
}
400408
}
401-
}
402-
});
409+
},
410+
);
403411

404412
const parseQuery = (query: { [key: string]: string }) => {
405413
return Object.keys(query).reduce((params, key) => {

src/controllers/api/tenthings/stats.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ const VOTE_STATS = new Set(["mostliked", "leastliked", "mostupvoted", "mostdownv
131131
const rankingCache = new Map<string, { data: RankRow[]; expiresAt: number }>();
132132
const CACHE_TTL = 10 * 60 * 1000;
133133

134-
tenthingsStatsRoute.get("/list-rankings/:stat", async (req: Request, res: Response) => {
134+
tenthingsStatsRoute.get("/list-rankings/:stat", async (req: Request<{ stat: string }>, res: Response) => {
135135
const config = LIST_STAT_CONFIG[req.params.stat];
136136
if (!config) {
137137
res.status(404).json({ error: "Unknown stat" });

src/controllers/api/users.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ usersRoute.get("/:id/login", async (req: Request, res: Response) => {
124124
}
125125
});
126126

127-
usersRoute.post("/:id/verification", async (req: Request, res: Response) => {
127+
usersRoute.post("/:id/verification", async (req: Request<{ id: string }>, res: Response) => {
128128
if (checkUser(req.params.id, res)) {
129129
const user = await User.findOne({ _id: res.locals.user?._id }).select("password");
130130
if (!user || user.banned) res.sendStatus(401);
@@ -137,7 +137,7 @@ usersRoute.post("/:id/verification", async (req: Request, res: Response) => {
137137
}
138138
});
139139

140-
usersRoute.post("/:id/telegram", async (req: Request, res: Response) => {
140+
usersRoute.post("/:id/telegram", async (req: Request<{ id: string }>, res: Response) => {
141141
if (checkUser(req.params.id, res)) {
142142
const data = req.body;
143143
if (verifyTelegramUser(data)) {
@@ -157,7 +157,7 @@ usersRoute.post("/:id/telegram", async (req: Request, res: Response) => {
157157
}
158158
});
159159

160-
usersRoute.post("/:id", async (req: Request, res: Response) => {
160+
usersRoute.post("/:id", async (req: Request<{ id: string }>, res: Response) => {
161161
if (checkUser(req.params.id, res)) {
162162
if (!res.locals.user?._id) res.sendStatus(400);
163163
else {
@@ -180,7 +180,7 @@ usersRoute.post("/:id", async (req: Request, res: Response) => {
180180
}
181181
});
182182

183-
usersRoute.post("/:id/password", async (req: Request, res: Response) => {
183+
usersRoute.post("/:id/password", async (req: Request<{ id: string }>, res: Response) => {
184184
if (checkUser(req.params.id, res)) {
185185
const user = await User.findOne({ _id: res.locals.user?._id }).select("username").select("password");
186186
if (!user || user.banned) res.sendStatus(401);
@@ -201,7 +201,7 @@ usersRoute.post("/:id/password", async (req: Request, res: Response) => {
201201
}
202202
});
203203

204-
usersRoute.post("/:id/username", async (req: Request, res: Response) => {
204+
usersRoute.post("/:id/username", async (req: Request<{ id: string }>, res: Response) => {
205205
if (checkUser(req.params.id, res)) {
206206
const user = await User.findOne({ _id: res.locals.user?._id }).select("username");
207207
if (!user || user.banned) res.sendStatus(401);

src/controllers/static.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ staticRoute.use(express.static(__dirname + "/../../images"));
99
staticRoute.use(express.static(__dirname + "/../../sounds"));
1010
staticRoute.use(express.static(__dirname + "/../../data"));
1111

12-
staticRoute.get("/*", function (_, res: Response) {
12+
staticRoute.get("/*splat", function (_, res: Response) {
1313
const reactIndex = path.resolve(__dirname, "../../dist/client/index.html");
1414
res.sendFile(reactIndex);
1515
});

0 commit comments

Comments
 (0)