diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 00000000..75e8d923 --- /dev/null +++ b/src/errors.js @@ -0,0 +1,20 @@ +'use strict'; + +class HttpError extends Error { + constructor(message, httpCode, friendlyMessage, data) { + super(message); + + this.name = 'HttpError'; + this.httpCode = httpCode; + + if (friendlyMessage) { + this.friendlyMessage = friendlyMessage; + } + + if (data) { + this.data = data; + } + } +} + +module.exports = { HttpError }; diff --git a/src/releases.js b/src/releases.js index d2c610e1..5443c39d 100644 --- a/src/releases.js +++ b/src/releases.js @@ -5,6 +5,7 @@ const inquirer = require('inquirer'); const logger = require('./logger')(__filename); const knex = require('./knex'); const { flushOrphanedMedia } = require('./media'); +const { HttpError } = require('./errors'); function curateRelease(release, withMedia = false, withPoster = true) { if (!release) { @@ -126,6 +127,10 @@ async function fetchScene(releaseId) { } async function fetchScenes(limit = 100) { + if (typeof limit !== 'number') { + throw new HttpError('Limit parameter needs to be a number', 400); + } + const releases = await knex('releases') .modify(withRelations, false, true) .limit(Math.min(limit, 1000000)); @@ -134,6 +139,14 @@ async function fetchScenes(limit = 100) { } async function searchScenes(query, limit = 100, relevance = 0) { + if (typeof limit !== 'number') { + throw new HttpError('Limit parameter needs to be a number', 400); + } + + if (typeof relevance !== 'number') { + throw new HttpError('Relevance parameter needs to be a number', 400); + } + const releases = await knex .select(knex.raw('search_results.rank as relevance')) .from(knex.raw('search_releases(:query) as search_results', { query })) diff --git a/src/web/error.js b/src/web/error.js new file mode 100644 index 00000000..736782cb --- /dev/null +++ b/src/web/error.js @@ -0,0 +1,17 @@ +'use strict'; + +const logger = require('../logger')(__filename); + +function errorHandler(error, req, res, _next) { + logger.warn(`Failed to fulfill request to ${req.path}: ${error.message}`); + + if (error.httpCode) { + res.status(error.httpCode).send(error.message); + + return; + } + + res.status(500).send('Oops... our server messed up. We will be investigating this incident, our apologies for the inconvenience.'); +} + +module.exports = errorHandler; diff --git a/src/web/releases.js b/src/web/releases.js index 90b2fe06..df3a9873 100644 --- a/src/web/releases.js +++ b/src/web/releases.js @@ -18,9 +18,11 @@ async function fetchSceneApi(req, res) { async function fetchScenesApi(req, res) { const query = req.query.query || req.query.q; + const limit = req.query.limit && Number(req.query.limit); + const relevance = req.query.relevance && Number(req.query.relevance); const releases = query - ? await searchScenes(query, req.query.limit, req.query.relevance) + ? await searchScenes(query, limit, relevance) : await fetchScenes(req.query.limit); res.send({ scenes: releases }); diff --git a/src/web/server.js b/src/web/server.js index 36e35d69..9108bc1f 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -17,6 +17,7 @@ const PgOrderByRelatedPlugin = require('@graphile-contrib/pg-order-by-related'); const logger = require('../logger')(__filename); const knex = require('../knex'); const { ActorPlugins, SitePlugins, ReleasePlugins } = require('./plugins/plugins'); +const errorHandler = require('./error'); const { fetchScene, @@ -124,6 +125,7 @@ async function initServer() { }); }); + router.use(errorHandler); app.use(router); const server = app.listen(config.web.port, config.web.host, () => {