diff --git a/.gitignore b/.gitignore index 911f7f45e9..78a2bf4294 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store website/public/gen website/public/common +website/public/apidoc node_modules *.swp .idea* diff --git a/test/api/v3/unit/middlewares/errorHandler.test.js b/test/api/v3/unit/middlewares/errorHandler.test.js index 912dd75d1c..783e0aff21 100644 --- a/test/api/v3/unit/middlewares/errorHandler.test.js +++ b/test/api/v3/unit/middlewares/errorHandler.test.js @@ -88,7 +88,7 @@ describe('errorHandler', () => { errorHandler(error, req, res, next); expect(logger.error).to.be.calledOnce; - expect(logger.error).to.be.calledWith(error.stack, { + expect(logger.error).to.be.calledWithExactly(error.stack, { originalUrl: req.originalUrl, headers: req.headers, body: req.body, diff --git a/test/api/v3/unit/middlewares/notFound.test.js b/test/api/v3/unit/middlewares/notFound.test.js new file mode 100644 index 0000000000..55064dbe0c --- /dev/null +++ b/test/api/v3/unit/middlewares/notFound.test.js @@ -0,0 +1,32 @@ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; + +import notFoundHandler from '../../../../../website/src/middlewares/api-v3/notFound'; + +import { NotFound } from '../../../../../website/src/libs/api-v3/errors'; + +describe('notFoundHandler', () => { + let res, req, next; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + + sandbox.stub(logger, 'error'); + }); + + it('sends NotFound error if the resource isn\'t found', () => { + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(404); + expect(res.json).to.be.calledWith({ + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/website/src/controllers/api-v3/example.js b/website/src/controllers/api-v3/example.js index f5e9f24b71..7ead22d794 100644 --- a/website/src/controllers/api-v3/example.js +++ b/website/src/controllers/api-v3/example.js @@ -1,6 +1,25 @@ // An example file to show how a controller should be structured let api = {}; +/** + * @api {get} /example/:id Request Example information + * @apiName GetExample + * @apiGroup Example + * + * @apiParam {Number} id Examples unique ID. + * + * @apiSuccess {String} firstname Firstname of the Example. + * @apiSuccess {String} lastname Lastname of the Example. + * + * @apiSuccessExample Success-Response: + * HTTP/1.1 200 OK + * { + * "firstname": "John", + * "lastname": "Doe" + * } + * + * @apiUse NotFound + */ api.exampleRoute = { method: 'GET', url: '/example/:param', @@ -12,4 +31,4 @@ api.exampleRoute = { }, }; -export default api; \ No newline at end of file +export default api; diff --git a/website/src/libs/api-v3/errors.js b/website/src/libs/api-v3/errors.js index 1439669d20..49e3a12c45 100644 --- a/website/src/libs/api-v3/errors.js +++ b/website/src/libs/api-v3/errors.js @@ -30,6 +30,25 @@ export class BadRequest extends CustomError { } } +/** + * @apiDefine NotFound + * @apiError NotFound The requested resource was not found. + * + * @apiErrorExample Error-Response: + * HTTP/1.1 404 Not Found + * { + * "error": "NotFound" + * } + */ +export class NotFound extends CustomError { + constructor (customMessage) { + super(); + this.name = this.constructor.name; + this.httpCode = 401; + this.message = customMessage || 'Not found.'; + } +} + // InternalError error with a 500 http error code // used when an unexpected, internal server error is thrown export class InternalServerError extends CustomError { diff --git a/website/src/middlewares/api-v3/errorHandler.js b/website/src/middlewares/api-v3/errorHandler.js index 5bf90f6969..5982284895 100644 --- a/website/src/middlewares/api-v3/errorHandler.js +++ b/website/src/middlewares/api-v3/errorHandler.js @@ -10,16 +10,6 @@ import { export default function errorHandler (err, req, res, next) { if (!err) return next(); - // Log the original error with some metadata - let stack = err.stack || err.message || err; - - logger.error(stack, { - originalUrl: req.originalUrl, - headers: req.headers, - body: req.body, - fullError: err, - }); - // In case of a CustomError class, use it's data // Otherwise try to identify the type of error (mongoose validation, mongodb unique, ...) // If we can't identify it, respond with a generic 500 error @@ -48,6 +38,16 @@ export default function errorHandler (err, req, res, next) { responseErr = new InternalServerError(); } + // Log the original error with some metadata + let stack = err.stack || err.message || err; + + logger.error(stack, { + originalUrl: req.originalUrl, + headers: req.headers, + body: req.body, + fullError: err, + }); + // TODO unless status >= 500 return data attached to errors return res .status(responseErr.httpCode) diff --git a/website/src/middlewares/api-v3/index.js b/website/src/middlewares/api-v3/index.js index 39847e67bf..0041e2dd54 100644 --- a/website/src/middlewares/api-v3/index.js +++ b/website/src/middlewares/api-v3/index.js @@ -4,6 +4,7 @@ import analytics from './analytics'; import errorHandler from './errorHandler'; import bodyParser from 'body-parser'; import routes from '../../libs/api-v3/setupRoutes'; +import notFoundHandler from './notFound'; export default function attachMiddlewares (app) { // Parse query parameters and json bodies @@ -15,6 +16,7 @@ export default function attachMiddlewares (app) { app.use(analytics); app.use(routes); + app.use(notFoundHandler); // Error handler middleware, define as the last one app.use(errorHandler); diff --git a/website/src/middlewares/api-v3/notFound.js b/website/src/middlewares/api-v3/notFound.js new file mode 100644 index 0000000000..733a247d1d --- /dev/null +++ b/website/src/middlewares/api-v3/notFound.js @@ -0,0 +1,7 @@ +import { + NotFound, +} from '../../libs/api-v3/errors'; + +export default function (req, res, next) { + next(new NotFound()); +} diff --git a/website/src/server.js b/website/src/server.js index 1142f27539..de899ab62d 100644 --- a/website/src/server.js +++ b/website/src/server.js @@ -4,7 +4,7 @@ import nconf from 'nconf'; import logger from './libs/api-v3/logger'; import express from 'express'; import http from 'http'; -// import path from 'path'; +import path from 'path'; // let swagger = require('swagger-node-express'); import autoinc from 'mongoose-id-autoinc'; import passport from 'passport'; @@ -73,7 +73,7 @@ passport.use(new FacebookStrategy({ }, (accessToken, refreshToken, profile, done) => done(null, profile))); // ------------ Server Configuration ------------ -// let publicDir = path.join(__dirname, '/../public'); +let publicDir = path.join(__dirname, '/../public'); app.set('port', nconf.get('PORT')); @@ -144,17 +144,18 @@ oldApp.use('/api/v1', require('./routes/api-v1')); oldApp.use('/export', require('./routes/dataexport')); require('./routes/api-v2/swagger')(swagger, v2); -var maxAge = IS_PROD ? 31536000000 : 0; // Cache emojis without copying them to build, they are too many -oldApp.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge })); -oldApp.use('/common/dist', express['static'](publicDir + "/../../common/dist", { maxAge: maxAge })); -oldApp.use('/common/audio', express['static'](publicDir + "/../../common/audio", { maxAge: maxAge })); -oldApp.use('/common/script/public', express['static'](publicDir + "/../../common/script/public", { maxAge: maxAge })); -oldApp.use('/common/img', express['static'](publicDir + "/../../common/img", { maxAge: maxAge })); -oldApp.use(express['static'](publicDir)); oldApp.use(require('./middlewares/api-v2/errorHandler')); */ +let maxAge = IS_PROD ? 31536000000 : 0; + +oldApp.use(express.static(path.join(__dirname, '/../build'), { maxAge })); +oldApp.use('/common/dist', express.static(`${publicDir}/../../common/dist`, { maxAge })); +oldApp.use('/common/audio', express.static(`${publicDir}/../../common/audio`, { maxAge })); +oldApp.use('/common/script/public', express.static(`${publicDir}/../../common/script/public`, { maxAge })); +oldApp.use('/common/img', express.static(`${publicDir}/../../common/img`, { maxAge })); +oldApp.use(express.static(publicDir)); server.on('request', app); server.listen(app.get('port'), () => {