4 * Here we extend the base API server to define our routes and any route-specific
5 * behavior (middlewares) before handing off to the manager.
8 const { Dingus
} = require('@squeep/api-dingus');
9 const path
= require('path');
10 const common
= require('./common');
11 const Enum
= require('./enum');
12 const { ServeStaticFile
} = require('./errors');
13 const Authenticator
= require('./authenticator');
14 const Manager
= require('./manager');
16 const _fileScope
= common
.fileScope(__filename
);
18 const defaultOptions
= {
19 ignoreTrailingSlash: true,
20 staticDirectory: path
.join(__dirname
, '..', 'static'),
21 createRequiresAuth: false,
25 class Service
extends Dingus
{
26 constructor(logger
, db
, options
= {}) {
27 super(logger
, { ...defaultOptions
, ...options
});
28 common
.setOptions(this, defaultOptions
, options
);
30 this.authenticator
= new Authenticator(logger
, db
, options
.authenticator
);
31 this._postRootAuth
= this.authenticator
[this.createRequiresAuth
? 'required' : 'optional'].bind(this.authenticator
);
33 this.manager
= new Manager(logger
, db
, options
.manager
);
35 this.responseTypes
.push(
36 // 'other/handled-type',
39 this.on('POST', '/', this.handlerPostRoot
.bind(this));
40 this.on(['GET', 'HEAD'], '/', this.handlerGetRoot
.bind(this));
41 this.on(['GET', 'HEAD'], '/:id', this.handlerGetId
.bind(this));
42 this.on('DELETE', '/:id', this.handlerDeleteId
.bind(this));
43 this.on('PUT', '/:id', this.handlerPutId
.bind(this));
44 this.on(['GET', 'HEAD'], '/:id/info', this.handlerGetIdInfo
.bind(this));
45 this.on(['GET', 'HEAD'], '/static/:file', this.handlerGetStatic
.bind(this));
46 this.on(['GET', 'HEAD'], '/admin/report', this.handlerGetAdminReport
.bind(this));
51 * Return a content-type appropriate rendering of an errorResponse object.
52 * @param {string} contentType content type of response
53 * @param {ResponseError|Exception} err
55 renderError(contentType
, err
) {
56 // eslint-disable-next-line sonarjs/no-small-switch
57 switch (contentType
) {
58 // Handle any additional content types here
61 return super.renderError(contentType
, err
);
67 * Parse rawBody from ctx as contentType into parsedBody.
68 * @param {string} contentType
71 parseBody(contentType
, ctx
, rawBody
) {
72 // eslint-disable-next-line sonarjs/no-small-switch
73 switch (contentType
) {
74 // Handle any additional content types here
77 super.parseBody(contentType
, ctx
, rawBody
);
83 * Called before every request handler.
84 * @param {http.ClientRequest} req
85 * @param {http.ServerResponse} res
88 async
preHandler(req
, res
, ctx
) {
89 super.preHandler(req
, res
, ctx
);
90 Dingus
.setEndBodyHandler(req
, res
, ctx
, this._endHandler
.bind(this));
94 * Do anything needed before sending.
95 * @param {http.ClientRequest} req
96 * @param {http.ServerResponse} res
99 _endHandler(req
, res
, ctx
) {
100 this.authenticator
.signResponse(req
, res
, ctx
);
104 * @param {http.ClientRequest} req
105 * @param {http.ServerResponse} res
106 * @param {object} ctx
108 async
handlerPostRoot(req
, res
, ctx
) {
109 const _scope
= _fileScope('handlerPostRoot');
110 this.logger
.debug(_scope
, 'called', { req
, ctx
});
112 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
113 await
this.ingestBody(req
, res
, ctx
, {
114 maximumBodySize: 1024 * 8,
116 await
this._postRootAuth(req
, res
, ctx
);
118 await
this.manager
.postRoot(res
, ctx
);
123 * @param {http.ClientRequest} req
124 * @param {http.ServerResponse} res
125 * @param {object} ctx
127 async
handlerGetRoot(req
, res
, ctx
) {
128 const _scope
= _fileScope('handlerGetRoot');
129 const responseTypes
= [
130 Enum
.ContentType
.TextHTML
,
132 this.logger
.debug(_scope
, 'called', { req
, ctx
});
134 Dingus
.setHeadHandler(req
, res
, ctx
);
136 this.setResponseType(responseTypes
, req
, res
, ctx
);
137 this.authenticator
.optional(req
, res
, ctx
);
139 // NB special case for this handler, pass in req so it can check headers
140 await
this.manager
.getRoot(req
, res
, ctx
);
145 * @param {http.ClientRequest} req
146 * @param {http.ServerResponse} res
147 * @param {object} ctx
149 async
handlerGetId(req
, res
, ctx
) {
150 const _scope
= _fileScope('handlerGetId');
151 this.logger
.debug(_scope
, 'called', { req
, ctx
});
153 Dingus
.setHeadHandler(req
, res
, ctx
);
155 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
156 this.authenticator
.optional(req
, res
, ctx
);
159 await
this.manager
.getById(res
, ctx
);
161 if (e
instanceof ServeStaticFile
163 return await
this.serveFile(req
, res
, ctx
, this.staticDirectory
, e
.file
);
171 * @param {http.ClientRequest} req
172 * @param {http.ServerResponse} res
173 * @param {object} ctx
175 async
handlerDeleteId(req
, res
, ctx
) {
176 const _scope
= _fileScope('handlerDeleteId');
177 this.logger
.debug(_scope
, 'called', { req
, ctx
});
179 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
180 await
this.authenticator
.required(req
, res
, ctx
);
182 await
this.manager
.deleteById(res
, ctx
);
187 * @param {http.ClientRequest} req
188 * @param {http.ServerResponse} res
189 * @param {object} ctx
191 async
handlerPutId(req
, res
, ctx
) {
192 const _scope
= _fileScope('handlerPutId');
193 this.logger
.debug(_scope
, 'called', { req
, ctx
});
195 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
196 await
this.ingestBody(req
, res
, ctx
);
197 await
this.authenticator
.required(req
, res
, ctx
);
199 await
this.manager
.putById(res
, ctx
);
204 * @param {http.ClientRequest} req
205 * @param {http.ServerResponse} res
206 * @param {object} ctx
208 async
handlerGetIdInfo(req
, res
, ctx
) {
209 const _scope
= _fileScope('handlerGetIdInfo');
210 this.logger
.debug(_scope
, 'called', { req
, ctx
});
212 Dingus
.setHeadHandler(req
, res
, ctx
);
214 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
215 this.authenticator
.optional(req
, res
, ctx
);
217 await
this.manager
.getByIdInfo(res
, ctx
);
222 * @param {http.ClientRequest} req
223 * @param {http.ServerResponse} res
224 * @param {object} ctx
226 async
handlerGetStatic(req
, res
, ctx
) {
227 const _scope
= _fileScope('handlerGetStatic');
228 this.logger
.debug(_scope
, 'called', { req
, ctx
});
230 Dingus
.setHeadHandler(req
, res
, ctx
);
232 // We set default response type to handle any errors, but will override for actual static content type.
233 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
234 this.authenticator
.optional(req
, res
, ctx
);
236 await
this.serveFile(req
, res
, ctx
, this.staticDirectory
, ctx
.params
.file
);
241 * @param {http.ClientRequest} req
242 * @param {http.ServerResponse} res
243 * @param {object} ctx
245 async
handlerGetAdminReport(req
, res
, ctx
) {
246 const _scope
= _fileScope('handlerAdminReport');
247 this.logger
.debug(_scope
, 'called', { req
, ctx
});
249 Dingus
.setHeadHandler(req
, res
, ctx
);
251 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
252 await
this.authenticator
.required(req
, res
, ctx
);
254 await
this.manager
.getAdminReport(res
, ctx
);
260 module
.exports
= Service
;