From 53b2cc68a2b62f61470a812edd88043b2ed0eb62 Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Wed, 9 Jun 2021 17:47:08 +0200 Subject: [PATCH] #215 better server error handling - server --- server/index.js | 5 +++++ server/routes/admin.js | 18 ++++++++-------- server/routes/banner.js | 5 +++-- server/routes/census.js | 17 +++++++-------- server/routes/images.js | 5 +++-- server/routes/inclusive.js | 26 +++++++++++------------ server/routes/nouns.js | 30 +++++++++++++-------------- server/routes/profile.js | 13 ++++++------ server/routes/pronounce.js | 5 +++-- server/routes/pronouns.js | 10 ++++----- server/routes/sources.js | 25 ++++++++++++----------- server/routes/terms.js | 26 +++++++++++------------ server/routes/user.js | 42 +++++++++++++++++++------------------- src/helpers.js | 4 ++++ 14 files changed, 123 insertions(+), 108 deletions(-) diff --git a/server/index.js b/server/index.js index cba54b6f5..a503e8cac 100644 --- a/server/index.js +++ b/server/index.js @@ -54,6 +54,11 @@ app.use(require('./routes/census').default); app.use(require('./routes/images').default); +app.use(function (err, req, res, next) { + console.error(err.stack); + res.status(500).send('Unexpected server error'); +}); + export default { path: '/api', handler: app, diff --git a/server/routes/admin.js b/server/routes/admin.js index fdb889fc9..3cb71d8aa 100644 --- a/server/routes/admin.js +++ b/server/routes/admin.js @@ -2,13 +2,13 @@ import { Router } from 'express'; import SQL from 'sql-template-strings'; import avatar from '../avatar'; import {config as socialLoginConfig} from "../social"; -import {buildDict, now, shuffle, sortByValue} from "../../src/helpers"; +import {buildDict, now, shuffle, sortByValue, handleErrorAsync} from "../../src/helpers"; import locales from '../../src/locales'; import {decodeTime} from "ulid"; const router = Router(); -router.get('/admin/list', async (req, res) => { +router.get('/admin/list', handleErrorAsync(async (req, res) => { const admins = await req.db.all(SQL` SELECT u.username, p.teamName, p.locale, u.id, u.email, u.avatarSource FROM users u @@ -39,9 +39,9 @@ router.get('/admin/list', async (req, res) => { } return res.json(adminsGroupped); -}); +})); -router.get('/admin/list/footer', async (req, res) => { +router.get('/admin/list/footer', handleErrorAsync(async (req, res) => { const fromDb = await req.db.all(SQL` SELECT u.username, p.footerName, p.footerAreas, p.locale FROM users u @@ -54,9 +54,9 @@ router.get('/admin/list/footer', async (req, res) => { const fromConfig = req.config.contact.authors || []; return res.json(shuffle([...fromDb, ...fromConfig])); -}); +})); -router.get('/admin/users', async (req, res) => { +router.get('/admin/users', handleErrorAsync(async (req, res) => { if (!req.isGranted('users')) { return res.status(401).json({error: 'Unauthorised'}); } @@ -96,7 +96,7 @@ router.get('/admin/users', async (req, res) => { } return res.json(groupedUsers); -}); +})); const formatMonth = d => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`; @@ -120,7 +120,7 @@ const buildChart = (rows) => { return chart; } -router.get('/admin/stats', async (req, res) => { +router.get('/admin/stats', handleErrorAsync(async (req, res) => { if (!req.isGranted('panel')) { return res.status(401).json({error: 'Unauthorised'}); } @@ -177,6 +177,6 @@ router.get('/admin/stats', async (req, res) => { } return res.json({ users, locales }); -}); +})); export default router; diff --git a/server/routes/banner.js b/server/routes/banner.js index 3c74dc304..4740144df 100644 --- a/server/routes/banner.js +++ b/server/routes/banner.js @@ -5,6 +5,7 @@ import { loadSuml } from '../loader'; import avatar from '../avatar'; import {buildPronoun, parsePronouns} from "../../src/buildPronoun"; import {loadTsv} from "../../src/tsv"; +import {handleErrorAsync} from "../../src/helpers"; const translations = loadSuml('translations'); @@ -26,7 +27,7 @@ const drawCircle = (context, image, x, y, size) => { const router = Router(); -router.get('/banner/:pronounName*.png', async (req, res) => { +router.get('/banner/:pronounName*.png', handleErrorAsync(async (req, res) => { const pronounName = req.params.pronounName + req.params[0]; const width = 1200 const height = 600 @@ -97,6 +98,6 @@ router.get('/banner/:pronounName*.png', async (req, res) => { context.fillText(pronounNameOptions.join('\n'), width / leftRatio + imageSize / 1.5, height / 2 + (pronounNameOptions.length <= 2 ? 72 : 24)) return res.set('content-type', mime).send(canvas.toBuffer(mime)); -}); +})); export default router; diff --git a/server/routes/census.js b/server/routes/census.js index a6fafef46..79792cb66 100644 --- a/server/routes/census.js +++ b/server/routes/census.js @@ -3,6 +3,7 @@ import SQL from 'sql-template-strings'; import sha1 from 'sha1'; import {ulid} from "ulid"; import Papa from 'papaparse'; +import {handleErrorAsync} from "../../src/helpers"; const getIp = req => { try { @@ -42,11 +43,11 @@ const hasFinished = async req => { const router = Router(); -router.get('/census/finished', async (req, res) => { +router.get('/census/finished', handleErrorAsync(async (req, res) => { return res.json(await hasFinished(req)); -}); +})); -router.post('/census/submit', async (req, res) => { +router.post('/census/submit', handleErrorAsync(async (req, res) => { const suspicious = await hasFinished(req); const id = ulid(); @@ -65,17 +66,17 @@ router.post('/census/submit', async (req, res) => { )`); return res.json(id); -}); +})); -router.get('/census/count', async (req, res) => { +router.get('/census/count', handleErrorAsync(async (req, res) => { return res.json((await req.db.get(SQL` SELECT COUNT(*) as c FROM census WHERE locale = ${req.config.locale} AND edition = ${req.config.census.edition} `)).c); -}); +})); -router.get('/census/export', async (req, res) => { +router.get('/census/export', handleErrorAsync(async (req, res) => { if (!req.isGranted('census')) { res.status(401).json({error: 'Unauthorised'}); } @@ -110,6 +111,6 @@ router.get('/census/export', async (req, res) => { } return res.set('content-type', 'text/csv').send(Papa.unparse(report)); -}); +})); export default router; diff --git a/server/routes/images.js b/server/routes/images.js index 5cabf1444..d7f60462b 100644 --- a/server/routes/images.js +++ b/server/routes/images.js @@ -2,6 +2,7 @@ import { Router } from 'express'; import {ulid} from "ulid"; import multer from 'multer'; import {loadImage, createCanvas} from 'canvas'; +import {handleErrorAsync} from "../../src/helpers"; import awsConfig from '../aws'; import S3 from 'aws-sdk/clients/s3'; @@ -43,7 +44,7 @@ const scaleDownTo = (image, size) => { const router = Router(); -router.post('/images/upload', multer({limits: {fileSize: 10 * 1024 * 1024}}).any('images[]', 12), async (req, res) => { +router.post('/images/upload', multer({limits: {fileSize: 10 * 1024 * 1024}}).any('images[]', 12), handleErrorAsync(async (req, res) => { const s3 = new S3(awsConfig); const ids = []; @@ -75,6 +76,6 @@ router.post('/images/upload', multer({limits: {fileSize: 10 * 1024 * 1024}}).any ids.push(id); } return res.json(ids); -}); +})); export default router; diff --git a/server/routes/inclusive.js b/server/routes/inclusive.js index 4c48f3528..67862275c 100644 --- a/server/routes/inclusive.js +++ b/server/routes/inclusive.js @@ -1,7 +1,7 @@ import { Router } from 'express'; import SQL from 'sql-template-strings'; import {ulid} from "ulid"; -import {isTroll} from "../../src/helpers"; +import {isTroll, handleErrorAsync} from "../../src/helpers"; const approve = async (db, id) => { const { base_id } = await db.get(SQL`SELECT base_id FROM inclusive WHERE id=${id}`); @@ -21,7 +21,7 @@ const approve = async (db, id) => { const router = Router(); -router.get('/inclusive', async (req, res) => { +router.get('/inclusive', handleErrorAsync(async (req, res) => { return res.json(await req.db.all(SQL` SELECT i.*, u.username AS author FROM inclusive i LEFT JOIN users u ON i.author_id = u.id @@ -30,9 +30,9 @@ router.get('/inclusive', async (req, res) => { AND i.deleted = 0 ORDER BY i.approved, i.insteadOf `)); -}); +})); -router.get('/inclusive/search/:term', async (req, res) => { +router.get('/inclusive/search/:term', handleErrorAsync(async (req, res) => { const term = '%' + req.params.term + '%'; return res.json(await req.db.all(SQL` SELECT i.*, u.username AS author FROM inclusive i @@ -43,9 +43,9 @@ router.get('/inclusive/search/:term', async (req, res) => { AND (i.insteadOf like ${term} OR i.say like ${term}) ORDER BY i.approved, i.insteadOf `)); -}); +})); -router.post('/inclusive/submit', async (req, res) => { +router.post('/inclusive/submit', handleErrorAsync(async (req, res) => { if (!(req.user && req.user.admin) && isTroll(JSON.stringify(req.body))) { return res.json('ok'); } @@ -66,9 +66,9 @@ router.post('/inclusive/submit', async (req, res) => { } return res.json('ok'); -}); +})); -router.post('/inclusive/hide/:id', async (req, res) => { +router.post('/inclusive/hide/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('inclusive')) { res.status(401).json({error: 'Unauthorised'}); } @@ -80,9 +80,9 @@ router.post('/inclusive/hide/:id', async (req, res) => { `); return res.json('ok'); -}); +})); -router.post('/inclusive/approve/:id', async (req, res) => { +router.post('/inclusive/approve/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('inclusive')) { res.status(401).json({error: 'Unauthorised'}); } @@ -90,9 +90,9 @@ router.post('/inclusive/approve/:id', async (req, res) => { await approve(req.db, req.params.id); return res.json('ok'); -}); +})); -router.post('/inclusive/remove/:id', async (req, res) => { +router.post('/inclusive/remove/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('inclusive')) { res.status(401).json({error: 'Unauthorised'}); } @@ -104,6 +104,6 @@ router.post('/inclusive/remove/:id', async (req, res) => { `); return res.json('ok'); -}); +})); export default router; diff --git a/server/routes/nouns.js b/server/routes/nouns.js index 95a3e106b..9abaee5ab 100644 --- a/server/routes/nouns.js +++ b/server/routes/nouns.js @@ -3,7 +3,7 @@ import SQL from 'sql-template-strings'; import {ulid} from "ulid"; import {createCanvas, loadImage, registerFont} from "canvas"; import {loadSuml} from "../loader"; -import {buildDict, isTroll} from "../../src/helpers"; +import {handleErrorAsync, isTroll} from "../../src/helpers"; const translations = loadSuml('translations'); @@ -68,7 +68,7 @@ const selectFragment = (sourcesMap, keyAndFragment) => { const router = Router(); -router.get('/nouns', async (req, res) => { +router.get('/nouns', handleErrorAsync(async (req, res) => { return res.json(await addVersions(req, await req.db.all(SQL` SELECT n.*, u.username AS author FROM nouns n LEFT JOIN users u ON n.author_id = u.id @@ -77,9 +77,9 @@ router.get('/nouns', async (req, res) => { AND n.approved >= ${req.isGranted('nouns') ? 0 : 1} ORDER BY n.approved, n.masc `))); -}); +})); -router.get('/nouns/search/:term', async (req, res) => { +router.get('/nouns/search/:term', handleErrorAsync(async (req, res) => { const term = '%' + req.params.term + '%'; return res.json(await addVersions(req, await req.db.all(SQL` SELECT n.*, u.username AS author FROM nouns n @@ -90,9 +90,9 @@ router.get('/nouns/search/:term', async (req, res) => { AND (n.masc like ${term} OR n.fem like ${term} OR n.neutr like ${term} OR n.mascPl like ${term} OR n.femPl like ${term} OR n.neutrPl like ${term}) ORDER BY n.approved, n.masc `))); -}); +})); -router.post('/nouns/submit', async (req, res) => { +router.post('/nouns/submit', handleErrorAsync(async (req, res) => { if (!(req.user && req.user.admin) && isTroll(JSON.stringify(req.body))) { return res.json('ok'); } @@ -114,9 +114,9 @@ router.post('/nouns/submit', async (req, res) => { } return res.json('ok'); -}); +})); -router.post('/nouns/hide/:id', async (req, res) => { +router.post('/nouns/hide/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('nouns')) { res.status(401).json({error: 'Unauthorised'}); } @@ -128,9 +128,9 @@ router.post('/nouns/hide/:id', async (req, res) => { `); return res.json('ok'); -}); +})); -router.post('/nouns/approve/:id', async (req, res) => { +router.post('/nouns/approve/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('nouns')) { res.status(401).json({error: 'Unauthorised'}); } @@ -138,9 +138,9 @@ router.post('/nouns/approve/:id', async (req, res) => { await approve(req.db, req.params.id); return res.json('ok'); -}); +})); -router.post('/nouns/remove/:id', async (req, res) => { +router.post('/nouns/remove/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('nouns')) { res.status(401).json({error: 'Unauthorised'}); } @@ -152,7 +152,7 @@ router.post('/nouns/remove/:id', async (req, res) => { `); return res.json('ok'); -}); +})); const findBaseForm = (noun, query) => { for (let form of ['masc', 'fem', 'neutr', 'mascPl', 'femPl', 'neutrPl']) { @@ -166,7 +166,7 @@ const findBaseForm = (noun, query) => { return null; } -router.get('/nouns/:word.png', async (req, res) => { +router.get('/nouns/:word.png', handleErrorAsync(async (req, res) => { const query = req.params.word.toLowerCase(); const term = '%' + query + '%'; const noun = (await req.db.all(SQL` @@ -233,6 +233,6 @@ router.get('/nouns/:word.png', async (req, res) => { context.fillText(translations.title, padding + 48, height - padding - 4); return res.set('content-type', mime).send(canvas.toBuffer(mime)); -}); +})); export default router; diff --git a/server/routes/profile.js b/server/routes/profile.js index 64a11008b..ba40a6787 100644 --- a/server/routes/profile.js +++ b/server/routes/profile.js @@ -3,6 +3,7 @@ import SQL from 'sql-template-strings'; import md5 from "js-md5"; import {ulid} from "ulid"; import avatar from "../avatar"; +import {handleErrorAsync} from "../../src/helpers"; const normalise = s => s.trim().toLowerCase(); @@ -58,11 +59,11 @@ const fetchProfiles = async (db, username, self) => { const router = Router(); -router.get('/profile/get/:username', async (req, res) => { +router.get('/profile/get/:username', handleErrorAsync(async (req, res) => { return res.json(await fetchProfiles(req.db, req.params.username, req.user && req.user.username === req.params.username)) -}); +})); -router.post('/profile/save', async (req, res) => { +router.post('/profile/save', handleErrorAsync(async (req, res) => { if (!req.user) { return res.status(401).json({error: 'Unauthorised'}); } @@ -97,12 +98,12 @@ router.post('/profile/save', async (req, res) => { } return res.json(await fetchProfiles(req.db, req.user.username, true)); -}); +})); -router.post('/profile/delete/:locale', async (req, res) => { +router.post('/profile/delete/:locale', handleErrorAsync(async (req, res) => { await req.db.get(SQL`DELETE FROM profiles WHERE userId = ${req.user.id} AND locale = ${req.params.locale}`); return res.json(await fetchProfiles(req.db, req.user.username, true)); -}); +})); export default router; diff --git a/server/routes/pronounce.js b/server/routes/pronounce.js index 2031c3b8d..1c3ca8132 100644 --- a/server/routes/pronounce.js +++ b/server/routes/pronounce.js @@ -3,6 +3,7 @@ import { loadTsv } from '../loader'; import {buildPronoun, parsePronouns} from "../../src/buildPronoun"; import {Example} from "../../src/classes"; import sha1 from 'sha1'; +import {handleErrorAsync} from "../../src/helpers"; import awsConfig from '../aws'; import Polly from 'aws-sdk/clients/polly'; @@ -10,7 +11,7 @@ import S3 from 'aws-sdk/clients/s3'; const router = Router(); -router.get('/pronounce/:voice/:pronoun*', async (req, res) => { +router.get('/pronounce/:voice/:pronoun*', handleErrorAsync(async (req, res) => { const pronounString = req.params.pronoun + req.params[0]; const pronoun = buildPronoun( parsePronouns(loadTsv('pronouns/pronouns')), @@ -65,6 +66,6 @@ router.get('/pronounce/:voice/:pronoun*', async (req, res) => { return res.set('content-type', pollyResponse.ContentType).send(pollyResponse.AudioStream); } -}); +})); export default router; diff --git a/server/routes/pronouns.js b/server/routes/pronouns.js index 6584ea6ef..032ee3760 100644 --- a/server/routes/pronouns.js +++ b/server/routes/pronouns.js @@ -1,7 +1,7 @@ import { Router } from 'express'; import { loadTsv } from '../loader'; import {buildPronoun, parsePronouns} from "../../src/buildPronoun"; -import {buildList} from "../../src/helpers"; +import {buildList, handleErrorAsync} from "../../src/helpers"; import {Example} from "../../src/classes"; const buildExample = e => new Example( @@ -33,16 +33,16 @@ const addExamples = (pronoun, examples) => { const router = Router(); -router.get('/pronouns', async (req, res) => { +router.get('/pronouns', handleErrorAsync(async (req, res) => { const pronouns = parsePronouns(loadTsv('pronouns/pronouns')); for (let pronoun in pronouns) { if (!pronouns.hasOwnProperty(pronoun)) { continue; } pronouns[pronoun].examples = addExamples(pronouns[pronoun], requestExamples(req.query.examples)) } return res.json(pronouns); -}); +})); -router.get('/pronouns/:pronoun*', async (req, res) => { +router.get('/pronouns/:pronoun*', handleErrorAsync(async (req, res) => { const pronoun = buildPronoun( parsePronouns(loadTsv('pronouns/pronouns')), req.params.pronoun + req.params[0], @@ -51,6 +51,6 @@ router.get('/pronouns/:pronoun*', async (req, res) => { pronoun.examples = addExamples(pronoun, requestExamples(req.query.examples)) } return res.json(pronoun); -}); +})); export default router; diff --git a/server/routes/sources.js b/server/routes/sources.js index 187e66a37..95f1fd761 100644 --- a/server/routes/sources.js +++ b/server/routes/sources.js @@ -1,6 +1,7 @@ import { Router } from 'express'; import SQL from "sql-template-strings"; import {ulid} from "ulid"; +import {handleErrorAsync} from "../../src/helpers"; const approve = async (db, id) => { const { base_id } = await db.get(SQL`SELECT base_id FROM sources WHERE id=${id}`); @@ -46,7 +47,7 @@ const linkOtherVersions = async (req, sources) => { const router = Router(); -router.get('/sources', async (req, res) => { +router.get('/sources', handleErrorAsync(async (req, res) => { let sql = SQL` SELECT s.*, u.username AS submitter FROM sources s LEFT JOIN users u ON s.submitter_id = u.id @@ -58,9 +59,9 @@ router.get('/sources', async (req, res) => { sql.append(SQL`AND s.pronouns LIKE ${'%' + req.query.pronoun + '%'}`) } return res.json(await linkOtherVersions(req, await req.db.all(sql))); -}); +})); -router.get('/sources/:id', async (req, res) => { +router.get('/sources/:id', handleErrorAsync(async (req, res) => { return res.json(await linkOtherVersions(req, await req.db.all(SQL` SELECT s.*, u.username AS submitter FROM sources s LEFT JOIN users u ON s.submitter_id = u.id @@ -69,9 +70,9 @@ router.get('/sources/:id', async (req, res) => { AND s.approved >= ${req.isGranted('sources') ? 0 : 1} AND s.id = ${req.params.id} `))); -}); +})); -router.post('/sources/submit', async (req, res) => { +router.post('/sources/submit', handleErrorAsync(async (req, res) => { const id = ulid(); await req.db.get(SQL` INSERT INTO sources (id, locale, pronouns, type, author, title, extra, year, fragments, comment, link, key, images, submitter_id, base_id) @@ -89,9 +90,9 @@ router.post('/sources/submit', async (req, res) => { } return res.json('ok'); -}); +})); -router.post('/sources/hide/:id', async (req, res) => { +router.post('/sources/hide/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('sources')) { res.status(401).json({error: 'Unauthorised'}); } @@ -103,9 +104,9 @@ router.post('/sources/hide/:id', async (req, res) => { `); return res.json('ok'); -}); +})); -router.post('/sources/approve/:id', async (req, res) => { +router.post('/sources/approve/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('sources')) { res.status(401).json({error: 'Unauthorised'}); } @@ -113,9 +114,9 @@ router.post('/sources/approve/:id', async (req, res) => { await approve(req.db, req.params.id); return res.json('ok'); -}); +})); -router.post('/sources/remove/:id', async (req, res) => { +router.post('/sources/remove/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('sources')) { res.status(401).json({error: 'Unauthorised'}); } @@ -127,6 +128,6 @@ router.post('/sources/remove/:id', async (req, res) => { `); return res.json('ok'); -}); +})); export default router; diff --git a/server/routes/terms.js b/server/routes/terms.js index f225adfd5..99cd42e9a 100644 --- a/server/routes/terms.js +++ b/server/routes/terms.js @@ -1,7 +1,7 @@ import { Router } from 'express'; import SQL from 'sql-template-strings'; import {ulid} from "ulid"; -import {isTroll} from "../../src/helpers"; +import {isTroll, handleErrorAsync} from "../../src/helpers"; const approve = async (db, id) => { const { base_id } = await db.get(SQL`SELECT base_id FROM terms WHERE id=${id}`); @@ -21,7 +21,7 @@ const approve = async (db, id) => { const router = Router(); -router.get('/terms', async (req, res) => { +router.get('/terms', handleErrorAsync(async (req, res) => { return res.json(await req.db.all(SQL` SELECT i.*, u.username AS author FROM terms i LEFT JOIN users u ON i.author_id = u.id @@ -30,9 +30,9 @@ router.get('/terms', async (req, res) => { AND i.deleted = 0 ORDER BY i.term `)); -}); +})); -router.get('/terms/search/:term', async (req, res) => { +router.get('/terms/search/:term', handleErrorAsync(async (req, res) => { const term = '%' + req.params.term + '%'; return res.json(await req.db.all(SQL` SELECT i.*, u.username AS author FROM terms i @@ -43,9 +43,9 @@ router.get('/terms/search/:term', async (req, res) => { AND (i.term like ${term} OR i.original like ${term}) ORDER BY i.term `)); -}); +})); -router.post('/terms/submit', async (req, res) => { +router.post('/terms/submit', handleErrorAsync(async (req, res) => { if (!(req.user && req.user.admin) && isTroll(JSON.stringify(req.body))) { return res.json('ok'); } @@ -66,9 +66,9 @@ router.post('/terms/submit', async (req, res) => { } return res.json('ok'); -}); +})); -router.post('/terms/hide/:id', async (req, res) => { +router.post('/terms/hide/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('terms')) { res.status(401).json({error: 'Unauthorised'}); } @@ -80,9 +80,9 @@ router.post('/terms/hide/:id', async (req, res) => { `); return res.json('ok'); -}); +})); -router.post('/terms/approve/:id', async (req, res) => { +router.post('/terms/approve/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('terms')) { res.status(401).json({error: 'Unauthorised'}); } @@ -90,9 +90,9 @@ router.post('/terms/approve/:id', async (req, res) => { await approve(req.db, req.params.id); return res.json('ok'); -}); +})); -router.post('/terms/remove/:id', async (req, res) => { +router.post('/terms/remove/:id', handleErrorAsync(async (req, res) => { if (!req.isGranted('terms')) { res.status(401).json({error: 'Unauthorised'}); } @@ -104,6 +104,6 @@ router.post('/terms/remove/:id', async (req, res) => { `); return res.json('ok'); -}); +})); export default router; diff --git a/server/routes/user.js b/server/routes/user.js index f557d1715..ca7da7edf 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -1,7 +1,7 @@ import { Router } from 'express'; import SQL from 'sql-template-strings'; import {ulid} from "ulid"; -import {buildDict, makeId, now} from "../../src/helpers"; +import {buildDict, makeId, now, handleErrorAsync} from "../../src/helpers"; import jwt from "../../src/jwt"; import mailer from "../../src/mailer"; import { loadSuml } from '../loader'; @@ -174,7 +174,7 @@ const router = Router(); router.use(reloadUser); -router.post('/user/init', async (req, res) => { +router.post('/user/init', handleErrorAsync(async (req, res) => { let user = undefined; let usernameOrEmail = req.body.usernameOrEmail; @@ -232,9 +232,9 @@ router.post('/user/init', async (req, res) => { return res.json({ token: jwt.sign({...payload, code: null, codeKey}, '15m'), }); -}); +})); -router.post('/user/validate', async (req, res) => { +router.post('/user/validate', handleErrorAsync(async (req, res) => { if (!req.rawUser || !req.rawUser.codeKey) { return res.json({error: 'user.tokenExpired'}); } @@ -251,9 +251,9 @@ router.post('/user/validate', async (req, res) => { await invalidateAuthenticator(req.db, authenticator); return res.json({token: await issueAuthentication(req.db, req.rawUser)}); -}); +})); -router.post('/user/change-username', async (req, res) => { +router.post('/user/change-username', handleErrorAsync(async (req, res) => { if (!req.user) { return res.status(401).json({error: 'Unauthorised'}); } @@ -270,9 +270,9 @@ router.post('/user/change-username', async (req, res) => { await req.db.get(SQL`UPDATE users SET username = ${req.body.username} WHERE id = ${req.user.id}`); return res.json({token: await issueAuthentication(req.db, req.user)}); -}); +})); -router.post('/user/change-email', async (req, res) => { +router.post('/user/change-email', handleErrorAsync(async (req, res) => { if (!req.user) { return res.status(401).json({error: 'Unauthorised'}); } @@ -319,9 +319,9 @@ router.post('/user/change-email', async (req, res) => { req.user.email = authenticator.payload.to; return res.json({token: await issueAuthentication(req.db, req.user)}); -}); +})); -router.post('/user/delete', async (req, res) => { +router.post('/user/delete', handleErrorAsync(async (req, res) => { if (!req.user) { return res.status(401).json({error: 'Unauthorised'}); } @@ -331,9 +331,9 @@ router.post('/user/delete', async (req, res) => { await req.db.get(SQL`DELETE FROM users WHERE id = ${req.user.id}`) return res.json(true); -}); +})); -router.post('/user/:id/set-roles', async (req, res) => { +router.post('/user/:id/set-roles', handleErrorAsync(async (req, res) => { if (!req.isGranted('*')) { return res.status(401).json({error: 'Unauthorised'}); } @@ -341,9 +341,9 @@ router.post('/user/:id/set-roles', async (req, res) => { await req.db.get(SQL`UPDATE users SET roles = ${req.body.roles} WHERE id = ${req.params.id}`); return res.json('ok'); -}); +})); -router.get('/user/social/:provider', async (req, res) => { +router.get('/user/social/:provider', handleErrorAsync(async (req, res) => { if (!req.session.grant || !req.session.grant.response || !req.session.grant.response.access_token || !socialLoginHandlers[req.params.provider]) { return res.status(400).redirect('/' + config.user.route); } @@ -382,9 +382,9 @@ router.get('/user/social/:provider', async (req, res) => { await saveAuthenticator(req.db, req.params.provider, dbUser, payload); return res.cookie('token', token).redirect('/' + config.user.route); -}); +})); -router.get('/user/social-connections', async (req, res) => { +router.get('/user/social-connections', handleErrorAsync(async (req, res) => { if (!req.user) { return res.status(401).json({error: 'Unauthorised'}); } @@ -401,9 +401,9 @@ router.get('/user/social-connections', async (req, res) => { yield [auth.type, JSON.parse(auth.payload)]; } })); -}); +})); -router.post('/user/social-connection/:provider/disconnect', async (req, res) => { +router.post('/user/social-connection/:provider/disconnect', handleErrorAsync(async (req, res) => { if (!req.user) { return res.status(401).json({error: 'Unauthorised'}); } @@ -418,9 +418,9 @@ router.post('/user/social-connection/:provider/disconnect', async (req, res) => await invalidateAuthenticator(req.db, auth.id) return res.json('ok'); -}); +})); -router.post('/user/set-avatar', async (req, res) => { +router.post('/user/set-avatar', handleErrorAsync(async (req, res) => { if (!req.user) { return res.status(401).json({error: 'Unauthorised'}); } @@ -432,6 +432,6 @@ router.post('/user/set-avatar', async (req, res) => { `) return res.json({token: await issueAuthentication(req.db, req.user)}); -}); +})); export default router; diff --git a/src/helpers.js b/src/helpers.js index 944a712e7..d5eb4e28d 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -200,3 +200,7 @@ export const isGranted = (user, locale, area) => { return false; } + +export const handleErrorAsync = func => (req, res, next) => { + func(req, res, next).catch((error) => next(error)); +};