From 20335e3f27992d36f7e0f950c7e76e1d0a64aced Mon Sep 17 00:00:00 2001 From: DetachHead Date: Sat, 3 Jul 2021 14:00:51 +1000 Subject: [PATCH] use express-promise-router to prevent requests timing out on unhandled promise rejections --- package-lock.json | 89 +++++++++++++++++++++++++++++++++++---------- package.json | 1 + src/app.ts | 92 ++++++++++++++++++++++++----------------------- 3 files changed, 120 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index a90825d..10ab124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "better-sqlite3": "^7.1.5", "dotenv": "^8.2.0", "express": "^4.17.1", + "express-promise-router": "^4.1.0", "express-rate-limit": "^5.1.3", "http": "0.0.0", "iso8601-duration": "^1.2.0", @@ -113,7 +114,7 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -129,7 +130,7 @@ "version": "3.4.33", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*" } @@ -138,7 +139,7 @@ "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", @@ -159,7 +160,7 @@ "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz", "integrity": "sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -176,7 +177,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", - "dev": true + "devOptional": true }, "node_modules/@types/mocha": { "version": "8.2.2", @@ -188,7 +189,7 @@ "version": "14.11.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.9.tgz", "integrity": "sha512-iXuiZ65PL5c8VAlF426GVJGKcsnAb2rW2037LJe3G6eM6nz35bK9QAUOH3Ic3kF4ZcKLpM02sFkSzCflIpoIKA==", - "dev": true + "devOptional": true }, "node_modules/@types/node-fetch": { "version": "2.5.7", @@ -215,13 +216,13 @@ "version": "6.9.5", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", - "dev": true + "devOptional": true }, "node_modules/@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", - "dev": true + "devOptional": true }, "node_modules/@types/redis": { "version": "2.8.28", @@ -262,7 +263,7 @@ "version": "1.13.5", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/express-serve-static-core": "*", "@types/mime": "*" @@ -1234,6 +1235,28 @@ "node": ">= 0.10.0" } }, + "node_modules/express-promise-router": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/express-promise-router/-/express-promise-router-4.1.0.tgz", + "integrity": "sha512-nvg0X1Rj8oajPPC+fG3t4e740aNmQZRZY6dRLbiiM56Dvd8213RJ4kaxhZVTdQLut+l4DZdfeJkyx2VENPMBdw==", + "dependencies": { + "is-promise": "^4.0.0", + "lodash.flattendeep": "^4.0.0", + "methods": "^1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/express": "^4.0.0", + "express": "^4.0.0" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, "node_modules/express-rate-limit": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz", @@ -1775,6 +1798,11 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "node_modules/is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", @@ -1896,6 +1924,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -4004,7 +4037,7 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", - "dev": true, + "devOptional": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -4020,7 +4053,7 @@ "version": "3.4.33", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", - "dev": true, + "devOptional": true, "requires": { "@types/node": "*" } @@ -4029,7 +4062,7 @@ "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==", - "dev": true, + "devOptional": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", @@ -4050,7 +4083,7 @@ "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz", "integrity": "sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA==", - "dev": true, + "devOptional": true, "requires": { "@types/node": "*", "@types/qs": "*", @@ -4067,7 +4100,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", - "dev": true + "devOptional": true }, "@types/mocha": { "version": "8.2.2", @@ -4079,7 +4112,7 @@ "version": "14.11.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.9.tgz", "integrity": "sha512-iXuiZ65PL5c8VAlF426GVJGKcsnAb2rW2037LJe3G6eM6nz35bK9QAUOH3Ic3kF4ZcKLpM02sFkSzCflIpoIKA==", - "dev": true + "devOptional": true }, "@types/node-fetch": { "version": "2.5.7", @@ -4106,13 +4139,13 @@ "version": "6.9.5", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", - "dev": true + "devOptional": true }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", - "dev": true + "devOptional": true }, "@types/redis": { "version": "2.8.28", @@ -4152,7 +4185,7 @@ "version": "1.13.5", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", - "dev": true, + "devOptional": true, "requires": { "@types/express-serve-static-core": "*", "@types/mime": "*" @@ -4951,6 +4984,16 @@ } } }, + "express-promise-router": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/express-promise-router/-/express-promise-router-4.1.0.tgz", + "integrity": "sha512-nvg0X1Rj8oajPPC+fG3t4e740aNmQZRZY6dRLbiiM56Dvd8213RJ4kaxhZVTdQLut+l4DZdfeJkyx2VENPMBdw==", + "requires": { + "is-promise": "^4.0.0", + "lodash.flattendeep": "^4.0.0", + "methods": "^1.0.0" + } + }, "express-rate-limit": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz", @@ -5354,6 +5397,11 @@ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", @@ -5451,6 +5499,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", diff --git a/package.json b/package.json index f8b69b9..8196bdb 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "better-sqlite3": "^7.1.5", "dotenv": "^8.2.0", "express": "^4.17.1", + "express-promise-router": "^4.1.0", "express-rate-limit": "^5.1.3", "http": "0.0.0", "iso8601-duration": "^1.2.0", diff --git a/src/app.ts b/src/app.ts index e02953c..a9e4df6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,4 @@ -import express, {Express, Request, RequestHandler, Response} from 'express'; +import express, {Request, RequestHandler, Response, Router} from 'express'; import {config} from './config'; import {oldSubmitSponsorTimes} from './routes/oldSubmitSponsorTimes'; import {oldGetVideoSponsorTimes} from './routes/oldGetVideoSponsorTimes'; @@ -33,16 +33,20 @@ import {postClearCache} from './routes/postClearCache'; import { addUnlistedVideo } from './routes/addUnlistedVideo'; import {postPurgeAllSegments} from './routes/postPurgeAllSegments'; import {getUserID} from './routes/getUserID'; +import ExpressPromiseRouter from 'express-promise-router'; export function createServer(callback: () => void) { // Create a service (the app object is just a callback). const app = express(); + const router = ExpressPromiseRouter() + app.use(router) + //setup CORS correctly - app.use(corsMiddleware); - app.use(loggerMiddleware); - app.use("/api/", apiCspMiddleware); - app.use(express.json()); + router.use(corsMiddleware); + router.use(loggerMiddleware); + router.use("/api/", apiCspMiddleware); + router.use(express.json()); if (config.userCounterURL) app.use(userCounter); @@ -52,12 +56,12 @@ export function createServer(callback: () => void) { // Set production mode app.set('env', config.mode || 'production'); - setupRoutes(app); + setupRoutes(router); return app.listen(config.port, callback); } -function setupRoutes(app: Express) { +function setupRoutes(router: Router) { // Rate limit endpoint lists const voteEndpoints: RequestHandler[] = [voteOnSponsorTime]; const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime]; @@ -67,96 +71,96 @@ function setupRoutes(app: Express) { } //add the get function - app.get('/api/getVideoSponsorTimes', oldGetVideoSponsorTimes); + router.get('/api/getVideoSponsorTimes', oldGetVideoSponsorTimes); //add the oldpost function - app.get('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); - app.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); + router.get('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); + router.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); //add the skip segments functions - app.get('/api/skipSegments', getSkipSegments); - app.post('/api/skipSegments', postSkipSegments); + router.get('/api/skipSegments', getSkipSegments); + router.post('/api/skipSegments', postSkipSegments); // add the privacy protecting skip segments functions - app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash); + router.get('/api/skipSegments/:prefix', getSkipSegmentsByHash); //voting endpoint - app.get('/api/voteOnSponsorTime', ...voteEndpoints); - app.post('/api/voteOnSponsorTime', ...voteEndpoints); + router.get('/api/voteOnSponsorTime', ...voteEndpoints); + router.post('/api/voteOnSponsorTime', ...voteEndpoints); //Endpoint when a submission is skipped - app.get('/api/viewedVideoSponsorTime', ...viewEndpoints); - app.post('/api/viewedVideoSponsorTime', ...viewEndpoints); + router.get('/api/viewedVideoSponsorTime', ...viewEndpoints); + router.post('/api/viewedVideoSponsorTime', ...viewEndpoints); //To set your username for the stats view - app.post('/api/setUsername', setUsername); + router.post('/api/setUsername', setUsername); //get what username this user has - app.get('/api/getUsername', getUsername); + router.get('/api/getUsername', getUsername); //Endpoint used to hide a certain user's data - app.post('/api/shadowBanUser', shadowBanUser); + router.post('/api/shadowBanUser', shadowBanUser); //Endpoint used to make a user a VIP user with special privileges - app.post('/api/addUserAsVIP', addUserAsVIP); + router.post('/api/addUserAsVIP', addUserAsVIP); //Gets all the views added up for one userID //Useful to see how much one user has contributed - app.get('/api/getViewsForUser', getViewsForUser); + router.get('/api/getViewsForUser', getViewsForUser); //Gets all the saved time added up (views * sponsor length) for one userID //Useful to see how much one user has contributed //In minutes - app.get('/api/getSavedTimeForUser', getSavedTimeForUser); + router.get('/api/getSavedTimeForUser', getSavedTimeForUser); - app.get('/api/getTopUsers', getTopUsers); + router.get('/api/getTopUsers', getTopUsers); //send out totals //send the total submissions, total views and total minutes saved - app.get('/api/getTotalStats', getTotalStats); + router.get('/api/getTotalStats', getTotalStats); - app.get('/api/getUserInfo', getUserInfo); - app.get('/api/userInfo', getUserInfo); + router.get('/api/getUserInfo', getUserInfo); + router.get('/api/userInfo', getUserInfo); //send out a formatted time saved total - app.get('/api/getDaysSavedFormatted', getDaysSavedFormatted); + router.get('/api/getDaysSavedFormatted', getDaysSavedFormatted); //submit video to lock categories - app.post('/api/noSegments', postLockCategories); - app.post('/api/lockCategories', postLockCategories); + router.post('/api/noSegments', postLockCategories); + router.post('/api/lockCategories', postLockCategories); - app.delete('/api/noSegments', deleteLockCategoriesEndpoint); - app.delete('/api/lockCategories', deleteLockCategoriesEndpoint); + router.delete('/api/noSegments', deleteLockCategoriesEndpoint); + router.delete('/api/lockCategories', deleteLockCategoriesEndpoint); //get if user is a vip - app.get('/api/isUserVIP', getIsUserVIP); + router.get('/api/isUserVIP', getIsUserVIP); //sent user a warning - app.post('/api/warnUser', postWarning); + router.post('/api/warnUser', postWarning); //get if user is a vip - app.post('/api/segmentShift', postSegmentShift); + router.post('/api/segmentShift', postSegmentShift); //get segment info - app.get('/api/segmentInfo', getSegmentInfo); + router.get('/api/segmentInfo', getSegmentInfo); //clear cache as VIP - app.post('/api/clearCache', postClearCache); + router.post('/api/clearCache', postClearCache); //purge all segments for VIP - app.post('/api/purgeAllSegments', postPurgeAllSegments); + router.post('/api/purgeAllSegments', postPurgeAllSegments); - app.post('/api/unlistedVideo', addUnlistedVideo); + router.post('/api/unlistedVideo', addUnlistedVideo); // get userID from username - app.get('/api/userID', getUserID); + router.get('/api/userID', getUserID); if (config.postgres) { - app.get('/database', (req, res) => dumpDatabase(req, res, true)); - app.get('/database.json', (req, res) => dumpDatabase(req, res, false)); - app.get('/database/*', redirectLink) + router.get('/database', (req, res) => dumpDatabase(req, res, true)); + router.get('/database.json', (req, res) => dumpDatabase(req, res, false)); + router.get('/database/*', redirectLink) } else { - app.get('/database.db', function (req: Request, res: Response) { + router.get('/database.db', function (req: Request, res: Response) { res.sendFile("./databases/sponsorTimes.db", {root: "./"}); }); }