| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | const becca = require("../becca/becca"); | 
					
						
							|  |  |  | const utils = require("../services/utils"); | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  | const eu = require("./etapi_utils"); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | const mappers = require("./mappers"); | 
					
						
							|  |  |  | const noteService = require("../services/notes"); | 
					
						
							|  |  |  | const TaskContext = require("../services/task_context"); | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  | const v = require("./validators"); | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  | const searchService = require("../services/search/services/search"); | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  | const SearchContext = require("../services/search/search_context"); | 
					
						
							| 
									
										
										
										
											2022-07-24 21:30:29 +02:00
										 |  |  | const zipExportService = require("../services/export/zip"); | 
					
						
							| 
									
										
										
										
											2023-01-11 23:18:51 +01:00
										 |  |  | const noteRevisionService = require("../services/note_revisions.js"); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | function register(router) { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     eu.route(router, 'get', '/etapi/notes', (req, res, next) => { | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |         const {search} = req.query; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!search?.trim()) { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |             throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory"); | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-12 21:14:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |         const searchParams = parseSearchParams(req); | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         const searchContext = new SearchContext(searchParams); | 
					
						
							| 
									
										
										
										
											2022-01-12 21:14:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         const searchResults = searchService.findResultsWithQuery(search, searchContext); | 
					
						
							|  |  |  |         const foundNotes = searchResults.map(sr => becca.notes[sr.noteId]); | 
					
						
							| 
									
										
										
										
											2022-01-12 21:14:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         const resp = { | 
					
						
							|  |  |  |             results: foundNotes.map(note => mappers.mapNoteToPojo(note)) | 
					
						
							|  |  |  |         }; | 
					
						
							| 
									
										
										
										
											2022-01-12 21:14:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         if (searchContext.debugInfo) { | 
					
						
							|  |  |  |             resp.debugInfo = searchContext.debugInfo; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-12 21:14:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         res.json(resp); | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     eu.route(router, 'get', '/etapi/notes/:noteId', (req, res, next) => { | 
					
						
							|  |  |  |         const note = eu.getAndCheckNote(req.params.noteId); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         res.json(mappers.mapNoteToPojo(note)); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |     const ALLOWED_PROPERTIES_FOR_CREATE_NOTE = { | 
					
						
							|  |  |  |         'parentNoteId': [v.mandatory, v.notNull, v.isNoteId], | 
					
						
							|  |  |  |         'title': [v.mandatory, v.notNull, v.isString], | 
					
						
							|  |  |  |         'type': [v.mandatory, v.notNull, v.isNoteType], | 
					
						
							|  |  |  |         'mime': [v.notNull, v.isString], | 
					
						
							|  |  |  |         'content': [v.notNull, v.isString], | 
					
						
							|  |  |  |         'notePosition': [v.notNull, v.isInteger], | 
					
						
							| 
									
										
										
										
											2022-02-16 05:49:49 +08:00
										 |  |  |         'prefix': [v.notNull, v.isString], | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |         'isExpanded': [v.notNull, v.isBoolean], | 
					
						
							|  |  |  |         'noteId': [v.notNull, v.isValidEntityId], | 
					
						
							|  |  |  |         'branchId': [v.notNull, v.isValidEntityId], | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2022-01-12 21:14:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => { | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |         const params = {}; | 
					
						
							| 
									
										
										
										
											2022-01-12 21:14:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |         eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             const resp = noteService.createNewNote(params); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-12 21:14:12 +01:00
										 |  |  |             res.status(201).json({ | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |                 note: mappers.mapNoteToPojo(resp.note), | 
					
						
							|  |  |  |                 branch: mappers.mapBranchToPojo(resp.branch) | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         catch (e) { | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |             return eu.sendError(res, 500, eu.GENERIC_CODE, e.message); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const ALLOWED_PROPERTIES_FOR_PATCH = { | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |         'title': [v.notNull, v.isString], | 
					
						
							|  |  |  |         'type': [v.notNull, v.isString], | 
					
						
							|  |  |  |         'mime': [v.notNull, v.isString] | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => { | 
					
						
							|  |  |  |         const note = eu.getAndCheckNote(req.params.noteId) | 
					
						
							| 
									
										
										
										
											2022-01-07 23:06:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         if (note.isProtected) { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |             throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI`); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-07 23:06:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         eu.validateAndPatch(note, req.body, ALLOWED_PROPERTIES_FOR_PATCH); | 
					
						
							| 
									
										
										
										
											2022-01-12 19:32:23 +01:00
										 |  |  |         note.save(); | 
					
						
							| 
									
										
										
										
											2022-01-07 23:06:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         res.json(mappers.mapNoteToPojo(note)); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     eu.route(router, 'delete' ,'/etapi/notes/:noteId', (req, res, next) => { | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |         const {noteId} = req.params; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const note = becca.getNote(noteId); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-08 12:01:54 +01:00
										 |  |  |         if (!note || note.isDeleted) { | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  |             return res.sendStatus(204); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-19 23:06:46 +02:00
										 |  |  |         note.deleteNote(null, new TaskContext('no-progress-reporting')); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         res.sendStatus(204); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-01-08 12:01:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     eu.route(router, 'get', '/etapi/notes/:noteId/content', (req, res, next) => { | 
					
						
							|  |  |  |         const note = eu.getAndCheckNote(req.params.noteId); | 
					
						
							| 
									
										
										
										
											2022-01-08 12:01:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const filename = utils.formatDownloadTitle(note.title, note.type, note.mime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         res.setHeader('Content-Disposition', utils.getContentDisposition(filename)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); | 
					
						
							|  |  |  |         res.setHeader('Content-Type', note.mime); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         res.send(note.getContent()); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     eu.route(router, 'put', '/etapi/notes/:noteId/content', (req, res, next) => { | 
					
						
							|  |  |  |         const note = eu.getAndCheckNote(req.params.noteId); | 
					
						
							| 
									
										
										
										
											2022-01-08 12:01:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         note.setContent(req.body); | 
					
						
							| 
									
										
										
										
											2023-01-11 23:22:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         noteService.scanForLinks(note); | 
					
						
							| 
									
										
										
										
											2022-01-08 12:01:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return res.sendStatus(204); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-07-24 21:30:29 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     eu.route(router, 'get' ,'/etapi/notes/:noteId/export', (req, res, next) => { | 
					
						
							|  |  |  |         const note = eu.getAndCheckNote(req.params.noteId); | 
					
						
							|  |  |  |         const format = req.query.format || "html"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!["html", "markdown"].includes(format)) { | 
					
						
							|  |  |  |             throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const taskContext = new TaskContext('no-progress-reporting'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // technically a branch is being exported (includes prefix), but it's such a minor difference yet usability pain
 | 
					
						
							|  |  |  |         // (e.g. branchIds are not seen in UI), that we export "note export" instead.
 | 
					
						
							|  |  |  |         const branch = note.getParentBranches()[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.log(note.getParentBranches()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         zipExportService.exportToZip(taskContext, branch, format, res); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2023-01-11 23:18:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     eu.route(router, 'post' ,'/etapi/notes/:noteId/note-revision', (req, res, next) => { | 
					
						
							|  |  |  |         const note = eu.getAndCheckNote(req.params.noteId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         note.saveNoteRevision(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return res.sendStatus(204); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  | function parseSearchParams(req) { | 
					
						
							|  |  |  |     const rawSearchParams = { | 
					
						
							| 
									
										
										
										
											2023-01-11 23:08:57 +01:00
										 |  |  |         fastSearch: parseBoolean(req.query, 'fastSearch'), | 
					
						
							|  |  |  |         includeArchivedNotes: parseBoolean(req.query, 'includeArchivedNotes'), | 
					
						
							|  |  |  |         ancestorNoteId: req.query['ancestorNoteId'], | 
					
						
							|  |  |  |         ancestorDepth: req.query['ancestorDepth'], // e.g. "eq5"
 | 
					
						
							|  |  |  |         orderBy: req.query['orderBy'], | 
					
						
							|  |  |  |         orderDirection: parseOrderDirection(req.query, 'orderDirection'), | 
					
						
							|  |  |  |         limit: parseInteger(req.query, 'limit'), | 
					
						
							|  |  |  |         debug: parseBoolean(req.query, 'debug') | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const searchParams = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const paramName of Object.keys(rawSearchParams)) { | 
					
						
							|  |  |  |         if (rawSearchParams[paramName] !== undefined) { | 
					
						
							|  |  |  |             searchParams[paramName] = rawSearchParams[paramName]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return searchParams; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const SEARCH_PARAM_ERROR = "SEARCH_PARAM_VALIDATION_ERROR"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function parseBoolean(obj, name) { | 
					
						
							|  |  |  |     if (!(name in obj)) { | 
					
						
							|  |  |  |         return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!['true', 'false'].includes(obj[name])) { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse boolean '${name}' value '${obj[name]}, allowed values are 'true' and 'false'`); | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return obj[name] === 'true'; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-11 02:28:42 +08:00
										 |  |  | function parseOrderDirection(obj, name) { | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |     if (!(name in obj)) { | 
					
						
							|  |  |  |         return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const integer = parseInt(obj[name]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!['asc', 'desc'].includes(obj[name])) { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse order direction value '${obj[name]}, allowed values are 'asc' and 'desc'`); | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return integer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-11 02:28:42 +08:00
										 |  |  | function parseInteger(obj, name) { | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |     if (!(name in obj)) { | 
					
						
							|  |  |  |         return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const integer = parseInt(obj[name]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (Number.isNaN(integer)) { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |         throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse integer '${name}' value '${obj[name]}`); | 
					
						
							| 
									
										
										
										
											2022-01-08 13:18:12 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return integer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-07 19:33:59 +01:00
										 |  |  | module.exports = { | 
					
						
							|  |  |  |     register | 
					
						
							| 
									
										
										
										
											2022-01-07 23:06:04 +01:00
										 |  |  | }; |