From c33bc7e12cd1a2761c7ad730c7c543f17108aaa4 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 8 Jan 2022 13:18:12 +0100 Subject: [PATCH] ETAPI search endpoint --- src/etapi/notes.js | 81 ++++++++++++++++++++++++++++++++++++++++++ test-etapi/search.http | 35 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 test-etapi/search.http diff --git a/src/etapi/notes.js b/src/etapi/notes.js index 2ad7cc36f..a264a1642 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -5,8 +5,24 @@ const mappers = require("./mappers"); const noteService = require("../services/notes"); const TaskContext = require("../services/task_context"); const validators = require("./validators"); +const searchService = require("../services/search/services/search"); function register(router) { + ru.route(router, 'get', '/etapi/notes', (req, res, next) => { + const {search} = req.query; + + if (!search?.trim()) { + throw new ru.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory"); + } + const searchParams = parseSearchParams(req); + + const foundNotes = searchService.searchNotes(search, searchParams); + + console.log(foundNotes.map(note => mappers.mapNoteToPojo(note))); + + res.json(foundNotes.map(note => mappers.mapNoteToPojo(note))); + }); + ru.route(router, 'get', '/etapi/notes/:noteId', (req, res, next) => { const note = ru.getAndCheckNote(req.params.noteId); @@ -85,6 +101,71 @@ function register(router) { }); } +function parseSearchParams(req) { + const rawSearchParams = { + 'fastSearch': parseBoolean(req.query, 'fastSearch'), + 'includeArchivedNotes': parseBoolean(req.query, 'includeArchivedNotes'), + 'ancestorNoteId': req.query['ancestorNoteId'], + 'ancestorDepth': parseInteger(req.query, 'ancestorDepth'), + 'orderBy': req.query['orderBy'], + 'orderDirection': parseOrderDirection(req.query, 'orderDirection'), + 'limit': parseInteger(req.query, 'limit'), + 'debug': parseBoolean(req.query, 'debug') + }; + + 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])) { + throw new ru.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse boolean '${name}' value '${obj[name]}, allowed values are 'true' and 'false'`); + } + + return obj[name] === 'true'; +} + +function parseInteger(obj, name) { + if (!(name in obj)) { + return undefined; + } + + const integer = parseInt(obj[name]); + + if (!['asc', 'desc'].includes(obj[name])) { + throw new ru.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse order direction value '${obj[name]}, allowed values are 'asc' and 'desc'`); + } + + return integer; +} + +function parseOrderDirection(obj, name) { + if (!(name in obj)) { + return undefined; + } + + const integer = parseInt(obj[name]); + + if (Number.isNaN(integer)) { + throw new ru.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse integer '${name}' value '${obj[name]}`); + } + + return integer; +} + module.exports = { register }; diff --git a/test-etapi/search.http b/test-etapi/search.http new file mode 100644 index 000000000..e7bbf9e9b --- /dev/null +++ b/test-etapi/search.http @@ -0,0 +1,35 @@ +POST {{triliumHost}}/etapi/create-note +Content-Type: application/json + +{ + "parentNoteId": "root", + "title": "title", + "type": "text", + "content": "{{$uuid}}" +} + +> {% client.global.set("createdNoteId", response.body.note.noteId); %} + +### + +GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content + +> {% client.global.set("content", response.body); %} + +### + +GET {{triliumHost}}/etapi/notes?search={{content}} + +> {% +client.assert(response.status === 200); +client.assert(response.body.length === 1); +%} + +### Same but with fast search which doesn't look in the content so 0 notes should be found + +GET {{triliumHost}}/etapi/notes?search={{content}}&fastSearch=true + +> {% +client.assert(response.status === 200); +client.assert(response.body.length === 0); +%}