1b09d2daf104a22426dc2fbd10f422d1f4f3094f
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
) {
72 // eslint-disable-next-line sonarjs/no-small-switch
73 switch (contentType
) {
74 // Handle any additional content types here
77 super.parseBody(contentType
, ctx
);
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: common
.requestLogData(req
), ctx
});
112 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
113 await
this.ingestBody(req
, res
, ctx
);
114 await
this._postRootAuth(req
, res
, ctx
);
116 await
this.manager
.postRoot(res
, ctx
);
121 * @param {http.ClientRequest} req
122 * @param {http.ServerResponse} res
123 * @param {object} ctx
125 async
handlerGetRoot(req
, res
, ctx
) {
126 const _scope
= _fileScope('handlerGetRoot');
127 const responseTypes
= [
128 Enum
.ContentType
.TextHTML
,
130 this.logger
.debug(_scope
, 'called', { req: common
.requestLogData(req
), ctx
});
132 Dingus
.setHeadHandler(req
, res
, ctx
);
134 this.setResponseType(responseTypes
, req
, res
, ctx
);
135 this.authenticator
.optional(req
, res
, ctx
);
137 // NB special case for this handler, pass in req so it can check headers
138 await
this.manager
.getRoot(req
, res
, ctx
);
143 * @param {http.ClientRequest} req
144 * @param {http.ServerResponse} res
145 * @param {object} ctx
147 async
handlerGetId(req
, res
, ctx
) {
148 const _scope
= _fileScope('handlerGetId');
149 this.logger
.debug(_scope
, 'called', { req: common
.requestLogData(req
), ctx
});
151 Dingus
.setHeadHandler(req
, res
, ctx
);
153 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
154 this.authenticator
.optional(req
, res
, ctx
);
157 await
this.manager
.getById(res
, ctx
);
159 if (e
instanceof ServeStaticFile
161 return await
this.serveFile(req
, res
, ctx
, this.staticDirectory
, e
.file
);
169 * @param {http.ClientRequest} req
170 * @param {http.ServerResponse} res
171 * @param {object} ctx
173 async
handlerDeleteId(req
, res
, ctx
) {
174 const _scope
= _fileScope('handlerDeleteId');
175 this.logger
.debug(_scope
, 'called', { req: common
.requestLogData(req
), ctx
});
177 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
178 await
this.authenticator
.required(req
, res
, ctx
);
180 await
this.manager
.deleteById(res
, ctx
);
185 * @param {http.ClientRequest} req
186 * @param {http.ServerResponse} res
187 * @param {object} ctx
189 async
handlerPutId(req
, res
, ctx
) {
190 const _scope
= _fileScope('handlerPutId');
191 this.logger
.debug(_scope
, 'called', { req: common
.requestLogData(req
), ctx
});
193 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
194 await
this.ingestBody(req
, res
, ctx
);
195 await
this.authenticator
.required(req
, res
, ctx
);
197 await
this.manager
.putById(res
, ctx
);
202 * @param {http.ClientRequest} req
203 * @param {http.ServerResponse} res
204 * @param {object} ctx
206 async
handlerGetIdInfo(req
, res
, ctx
) {
207 const _scope
= _fileScope('handlerGetIdInfo');
208 this.logger
.debug(_scope
, 'called', { req: common
.requestLogData(req
), ctx
});
210 Dingus
.setHeadHandler(req
, res
, ctx
);
212 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
213 this.authenticator
.optional(req
, res
, ctx
);
215 await
this.manager
.getByIdInfo(res
, ctx
);
220 * @param {http.ClientRequest} req
221 * @param {http.ServerResponse} res
222 * @param {object} ctx
224 async
handlerGetStatic(req
, res
, ctx
) {
225 const _scope
= _fileScope('handlerGetStatic');
226 this.logger
.debug(_scope
, 'called', { req: common
.requestLogData(req
), ctx
});
228 Dingus
.setHeadHandler(req
, res
, ctx
);
230 // We set default response type to handle any errors, but will override for actual static content type.
231 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
232 this.authenticator
.optional(req
, res
, ctx
);
234 await
this.serveFile(req
, res
, ctx
, this.staticDirectory
, ctx
.params
.file
);
239 * @param {http.ClientRequest} req
240 * @param {http.ServerResponse} res
241 * @param {object} ctx
243 async
handlerGetAdminReport(req
, res
, ctx
) {
244 const _scope
= _fileScope('handlerAdminReport');
245 this.logger
.debug(_scope
, 'called', { req: common
.requestLogData(req
), ctx
});
247 Dingus
.setHeadHandler(req
, res
, ctx
);
249 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
250 await
this.authenticator
.required(req
, res
, ctx
);
252 await
this.manager
.getAdminReport(res
, ctx
);
258 module
.exports
= Service
;