| 
									
										
										
										
											2023-11-22 19:34:48 +01:00
										 |  |  | const cls = require('../services/cls.js'); | 
					
						
							|  |  |  | const sql = require('../services/sql.js'); | 
					
						
							|  |  |  | const log = require('../services/log.js'); | 
					
						
							|  |  |  | const becca = require('../becca/becca.js'); | 
					
						
							|  |  |  | const etapiTokenService = require('../services/etapi_tokens.js'); | 
					
						
							|  |  |  | const config = require('../services/config.js'); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | const GENERIC_CODE = "GENERIC"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  | const noAuthentication = config.General && config.General.noAuthentication === true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | class EtapiError extends Error { | 
					
						
							|  |  |  |     constructor(statusCode, code, message) { | 
					
						
							|  |  |  |         super(); | 
					
						
							| 
									
										
										
										
											2022-01-17 23:47:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         this.statusCode = statusCode; | 
					
						
							|  |  |  |         this.code = code; | 
					
						
							|  |  |  |         this.message = message; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function sendError(res, statusCode, code, message) { | 
					
						
							|  |  |  |     return res | 
					
						
							|  |  |  |         .set('Content-Type', 'application/json') | 
					
						
							|  |  |  |         .status(statusCode) | 
					
						
							|  |  |  |         .send(JSON.stringify({ | 
					
						
							|  |  |  |             "status": statusCode, | 
					
						
							|  |  |  |             "code": code, | 
					
						
							|  |  |  |             "message": message | 
					
						
							|  |  |  |         })); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function checkEtapiAuth(req, res, next) { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     if (noAuthentication || etapiTokenService.isValidAuthHeader(req.headers.authorization)) { | 
					
						
							|  |  |  |         next(); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         sendError(res, 401, "NOT_AUTHENTICATED", "Not authenticated"); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  | function processRequest(req, res, routeHandler, next, method, path) { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |         cls.namespace.bindEmitter(req); | 
					
						
							|  |  |  |         cls.namespace.bindEmitter(res); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         cls.init(() => { | 
					
						
							|  |  |  |             cls.set('componentId', "etapi"); | 
					
						
							|  |  |  |             cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |             const cb = () => routeHandler(req, res, next); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |             return sql.transactional(cb); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |         log.error(`${method} ${path} threw exception ${e.message} with stacktrace: ${e.stack}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (e instanceof EtapiError) { | 
					
						
							|  |  |  |             sendError(res, e.statusCode, e.code, e.message); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             sendError(res, 500, GENERIC_CODE, e.message); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function route(router, method, path, routeHandler) { | 
					
						
							|  |  |  |     router[method](path, checkEtapiAuth, (req, res, next) => processRequest(req, res, routeHandler, next, method, path)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 11:50:58 +02:00
										 |  |  | function NOT_AUTHENTICATED_ROUTE(router, method, path, middleware, routeHandler) { | 
					
						
							|  |  |  |     router[method](path, ...middleware, (req, res, next) => processRequest(req, res, routeHandler, next, method, path)); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getAndCheckNote(noteId) { | 
					
						
							|  |  |  |     const note = becca.getNote(noteId); | 
					
						
							| 
									
										
										
										
											2022-01-17 23:47:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     if (note) { | 
					
						
							|  |  |  |         return note; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							| 
									
										
										
										
											2023-06-05 09:23:42 +02:00
										 |  |  |         throw new EtapiError(404, "NOTE_NOT_FOUND", `Note '${noteId}' not found.`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getAndCheckAttachment(attachmentId) { | 
					
						
							|  |  |  |     const attachment = becca.getAttachment(attachmentId, {includeContentLength: true}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (attachment) { | 
					
						
							|  |  |  |         return attachment; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							|  |  |  |         throw new EtapiError(404, "ATTACHMENT_NOT_FOUND", `Attachment '${attachmentId}' not found.`); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getAndCheckBranch(branchId) { | 
					
						
							|  |  |  |     const branch = becca.getBranch(branchId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (branch) { | 
					
						
							|  |  |  |         return branch; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							| 
									
										
										
										
											2023-06-05 09:23:42 +02:00
										 |  |  |         throw new EtapiError(404, "BRANCH_NOT_FOUND", `Branch '${branchId}' not found.`); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getAndCheckAttribute(attributeId) { | 
					
						
							|  |  |  |     const attribute = becca.getAttribute(attributeId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (attribute) { | 
					
						
							|  |  |  |         return attribute; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							| 
									
										
										
										
											2023-06-05 09:23:42 +02:00
										 |  |  |         throw new EtapiError(404, "ATTRIBUTE_NOT_FOUND", `Attribute '${attributeId}' not found.`); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  | function validateAndPatch(target, source, allowedProperties) { | 
					
						
							|  |  |  |     for (const key of Object.keys(source)) { | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         if (!(key in allowedProperties)) { | 
					
						
							| 
									
										
										
										
											2022-07-10 22:09:13 +02:00
										 |  |  |             throw new EtapiError(400, "PROPERTY_NOT_ALLOWED", `Property '${key}' is not allowed for this method.`); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |             for (const validator of allowedProperties[key]) { | 
					
						
							|  |  |  |                 const validationResult = validator(source[key]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (validationResult) { | 
					
						
							| 
									
										
										
										
											2023-06-05 09:23:42 +02:00
										 |  |  |                     throw new EtapiError(400, "PROPERTY_VALIDATION_ERROR", `Validation failed on property '${key}': ${validationResult}.`); | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-17 23:47:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     // validation passed, let's patch
 | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |     for (const propName of Object.keys(source)) { | 
					
						
							|  |  |  |         target[propName] = source[propName]; | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = { | 
					
						
							|  |  |  |     EtapiError, | 
					
						
							|  |  |  |     sendError, | 
					
						
							|  |  |  |     route, | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     NOT_AUTHENTICATED_ROUTE, | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     GENERIC_CODE, | 
					
						
							|  |  |  |     validateAndPatch, | 
					
						
							|  |  |  |     getAndCheckNote, | 
					
						
							|  |  |  |     getAndCheckBranch, | 
					
						
							| 
									
										
										
										
											2023-06-05 09:23:42 +02:00
										 |  |  |     getAndCheckAttribute, | 
					
						
							|  |  |  |     getAndCheckAttachment | 
					
						
							| 
									
										
										
										
											2022-01-09 20:16:39 +01:00
										 |  |  | } |