diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 48561254a..3423097d1 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -5,100 +5,88 @@ const router = express.Router(); const auth = require('../../services/auth'); const sql = require('../../services/sql'); const notes = require('../../services/notes'); -const log = require('../../services/log'); const utils = require('../../services/utils'); const protected_session = require('../../services/protected_session'); const tree = require('../../services/tree'); const sync_table = require('../../services/sync_table'); const wrap = require('express-promise-wrap').wrap; -router.get('/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => { +async function getNote(req) { const noteId = req.params.noteId; - const detail = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); + const note = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); - if (!detail) { - log.info("Note " + noteId + " has not been found."); - - return res.status(404).send({}); + if (!note) { + return [404, "Note " + noteId + " has not been found."]; } - protected_session.decryptNote(req, detail); + protected_session.decryptNote(req, note); - if (detail.type === 'file') { + if (note.type === 'file') { // no need to transfer (potentially large) file payload for this request - detail.content = null; + note.content = null; } - res.send(detail); -})); + return note; +} -router.post('/:parentNoteId/children', auth.checkApiAuth, wrap(async (req, res, next) => { +async function createNote(req) { const sourceId = req.headers.source_id; const parentNoteId = req.params.parentNoteId; const newNote = req.body; - await sql.doInTransaction(async () => { - const { noteId, branchId, note } = await notes.createNewNote(parentNoteId, newNote, req, sourceId); + const { noteId, branchId, note } = await notes.createNewNote(parentNoteId, newNote, req, sourceId); - res.send({ - 'noteId': noteId, - 'branchId': branchId, - 'note': note - }); - }); -})); + return { + 'noteId': noteId, + 'branchId': branchId, + 'note': note + }; +} -router.put('/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => { +async function updateNote(req) { const note = req.body; const noteId = req.params.noteId; const sourceId = req.headers.source_id; const dataKey = protected_session.getDataKey(req); - await sql.doInTransaction(async () => { - await notes.updateNote(noteId, note, dataKey, sourceId); - }); + await notes.updateNote(noteId, note, dataKey, sourceId); +} - res.send({}); -})); - -router.put('/:noteId/sort', auth.checkApiAuth, wrap(async (req, res, next) => { +async function sortNotes(req) { const noteId = req.params.noteId; const sourceId = req.headers.source_id; const dataKey = protected_session.getDataKey(req); await tree.sortNotesAlphabetically(noteId, dataKey, sourceId); +} - res.send({}); -})); - -router.put('/:noteId/protect-sub-tree/:isProtected', auth.checkApiAuth, wrap(async (req, res, next) => { +async function protectBranch(req) { const noteId = req.params.noteId; const isProtected = !!parseInt(req.params.isProtected); const dataKey = protected_session.getDataKey(req); const sourceId = req.headers.source_id; - await sql.doInTransaction(async () => { - await notes.protectNoteRecursively(noteId, dataKey, isProtected, sourceId); - }); + await notes.protectNoteRecursively(noteId, dataKey, isProtected, sourceId); +} - res.send({}); -})); - -router.put(/\/(.*)\/type\/(.*)\/mime\/(.*)/, auth.checkApiAuth, wrap(async (req, res, next) => { +async function setNoteTypeMime(req) { const noteId = req.params[0]; const type = req.params[1]; const mime = req.params[2]; const sourceId = req.headers.source_id; - await sql.doInTransaction(async () => { - await sql.execute("UPDATE notes SET type = ?, mime = ?, dateModified = ? WHERE noteId = ?", - [type, mime, utils.nowDate(), noteId]); + await sql.execute("UPDATE notes SET type = ?, mime = ?, dateModified = ? WHERE noteId = ?", + [type, mime, utils.nowDate(), noteId]); - await sync_table.addNoteSync(noteId, sourceId); - }); + await sync_table.addNoteSync(noteId, sourceId); +} - res.send({}); -})); - -module.exports = router; \ No newline at end of file +module.exports = { + getNote, + updateNote, + createNote, + sortNotes, + protectBranch, + setNoteTypeMime +}; \ No newline at end of file diff --git a/src/routes/api/tree.js b/src/routes/api/tree.js index 9d16e022b..a23b4009e 100644 --- a/src/routes/api/tree.js +++ b/src/routes/api/tree.js @@ -1,17 +1,13 @@ "use strict"; -const express = require('express'); -const router = express.Router(); const sql = require('../../services/sql'); const options = require('../../services/options'); const utils = require('../../services/utils'); -const auth = require('../../services/auth'); const config = require('../../services/config'); const protected_session = require('../../services/protected_session'); const sync_table = require('../../services/sync_table'); -const wrap = require('express-promise-wrap').wrap; -router.get('/', auth.checkApiAuth, wrap(async (req, res, next) => { +async function getTree(req) { const branches = await sql.getRows(` SELECT branchId, @@ -27,15 +23,13 @@ router.get('/', auth.checkApiAuth, wrap(async (req, res, next) => { ORDER BY notePosition`); - let notes = [{ + const notes = [{ noteId: 'root', title: 'root', isProtected: false, type: 'none', mime: 'none' - }]; - - notes = notes.concat(await sql.getRows(` + }].concat(await sql.getRows(` SELECT notes.noteId, notes.title, @@ -58,15 +52,15 @@ router.get('/', auth.checkApiAuth, wrap(async (req, res, next) => { note.isProtected = !!note.isProtected; }); - res.send({ + return { instanceName: config.General ? config.General.instanceName : null, branches: branches, notes: notes, start_note_path: await options.getOption('start_note_path') - }); -})); + }; +} -router.put('/:branchId/set-prefix', auth.checkApiAuth, wrap(async (req, res, next) => { +async function setPrefix(req) { const branchId = req.params.branchId; const sourceId = req.headers.source_id; const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix; @@ -77,7 +71,10 @@ router.put('/:branchId/set-prefix', auth.checkApiAuth, wrap(async (req, res, nex await sync_table.addBranchSync(branchId, sourceId); }); - res.send({}); -})); + return {}; +} -module.exports = router; +module.exports = { + getTree, + setPrefix +}; diff --git a/src/routes/api/tree_changes.js b/src/routes/api/tree_changes.js index f0db162ba..743743e81 100644 --- a/src/routes/api/tree_changes.js +++ b/src/routes/api/tree_changes.js @@ -15,15 +15,17 @@ const wrap = require('express-promise-wrap').wrap; * for not deleted note trees. There may be multiple deleted note-parent note relationships. */ -router.put('/:branchId/move-to/:parentNoteId', auth.checkApiAuth, wrap(async (req, res, next) => { +async function moveBranchToParent(req) { const branchId = req.params.branchId; const parentNoteId = req.params.parentNoteId; const sourceId = req.headers.source_id; const noteToMove = await tree.getBranch(branchId); - if (!await tree.validateParentChild(res, parentNoteId, noteToMove.noteId, branchId)) { - return; + const validationResult = await tree.validateParentChild(parentNoteId, noteToMove.noteId, branchId); + + if (!validationResult.success) { + return [400, validationResult]; } const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentNoteId]); @@ -31,17 +33,15 @@ router.put('/:branchId/move-to/:parentNoteId', auth.checkApiAuth, wrap(async (re const now = utils.nowDate(); - await sql.doInTransaction(async () => { - await sql.execute("UPDATE branches SET parentNoteId = ?, notePosition = ?, dateModified = ? WHERE branchId = ?", - [parentNoteId, newNotePos, now, branchId]); + await sql.execute("UPDATE branches SET parentNoteId = ?, notePosition = ?, dateModified = ? WHERE branchId = ?", + [parentNoteId, newNotePos, now, branchId]); - await sync_table.addBranchSync(branchId, sourceId); - }); + await sync_table.addBranchSync(branchId, sourceId); - res.send({ success: true }); -})); + return { success: true }; +} -router.put('/:branchId/move-before/:beforeBranchId', auth.checkApiAuth, wrap(async (req, res, next) => { +async function moveBranchBeforeNote(req) { const branchId = req.params.branchId; const beforeBranchId = req.params.beforeBranchId; const sourceId = req.headers.source_id; @@ -49,28 +49,28 @@ router.put('/:branchId/move-before/:beforeBranchId', auth.checkApiAuth, wrap(asy const noteToMove = await tree.getBranch(branchId); const beforeNote = await tree.getBranch(beforeBranchId); - if (!await tree.validateParentChild(res, beforeNote.parentNoteId, noteToMove.noteId, branchId)) { - return; + const validationResult = await tree.validateParentChild(beforeNote.parentNoteId, noteToMove.noteId, branchId); + + if (!validationResult.success) { + return [400, validationResult]; } - await sql.doInTransaction(async () => { - // we don't change dateModified so other changes are prioritized in case of conflict - // also we would have to sync all those modified note trees otherwise hash checks would fail - await sql.execute("UPDATE branches SET notePosition = notePosition + 1 WHERE parentNoteId = ? AND notePosition >= ? AND isDeleted = 0", - [beforeNote.parentNoteId, beforeNote.notePosition]); + // we don't change dateModified so other changes are prioritized in case of conflict + // also we would have to sync all those modified note trees otherwise hash checks would fail + await sql.execute("UPDATE branches SET notePosition = notePosition + 1 WHERE parentNoteId = ? AND notePosition >= ? AND isDeleted = 0", + [beforeNote.parentNoteId, beforeNote.notePosition]); - await sync_table.addNoteReorderingSync(beforeNote.parentNoteId, sourceId); + await sync_table.addNoteReorderingSync(beforeNote.parentNoteId, sourceId); - await sql.execute("UPDATE branches SET parentNoteId = ?, notePosition = ?, dateModified = ? WHERE branchId = ?", - [beforeNote.parentNoteId, beforeNote.notePosition, utils.nowDate(), branchId]); + await sql.execute("UPDATE branches SET parentNoteId = ?, notePosition = ?, dateModified = ? WHERE branchId = ?", + [beforeNote.parentNoteId, beforeNote.notePosition, utils.nowDate(), branchId]); - await sync_table.addBranchSync(branchId, sourceId); - }); + await sync_table.addBranchSync(branchId, sourceId); - res.send({ success: true }); -})); + return { success: true }; +} -router.put('/:branchId/move-after/:afterBranchId', auth.checkApiAuth, wrap(async (req, res, next) => { +async function moveBranchAfterNote(req) { const branchId = req.params.branchId; const afterBranchId = req.params.afterBranchId; const sourceId = req.headers.source_id; @@ -78,46 +78,43 @@ router.put('/:branchId/move-after/:afterBranchId', auth.checkApiAuth, wrap(async const noteToMove = await tree.getBranch(branchId); const afterNote = await tree.getBranch(afterBranchId); - if (!await tree.validateParentChild(res, afterNote.parentNoteId, noteToMove.noteId, branchId)) { - return; + const validationResult = await tree.validateParentChild(afterNote.parentNoteId, noteToMove.noteId, branchId); + + if (!validationResult.success) { + return [400, validationResult]; } - await sql.doInTransaction(async () => { - // we don't change dateModified so other changes are prioritized in case of conflict - // also we would have to sync all those modified note trees otherwise hash checks would fail - await sql.execute("UPDATE branches SET notePosition = notePosition + 1 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0", - [afterNote.parentNoteId, afterNote.notePosition]); + // we don't change dateModified so other changes are prioritized in case of conflict + // also we would have to sync all those modified note trees otherwise hash checks would fail + await sql.execute("UPDATE branches SET notePosition = notePosition + 1 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0", + [afterNote.parentNoteId, afterNote.notePosition]); - await sync_table.addNoteReorderingSync(afterNote.parentNoteId, sourceId); + await sync_table.addNoteReorderingSync(afterNote.parentNoteId, sourceId); - await sql.execute("UPDATE branches SET parentNoteId = ?, notePosition = ?, dateModified = ? WHERE branchId = ?", - [afterNote.parentNoteId, afterNote.notePosition + 1, utils.nowDate(), branchId]); + await sql.execute("UPDATE branches SET parentNoteId = ?, notePosition = ?, dateModified = ? WHERE branchId = ?", + [afterNote.parentNoteId, afterNote.notePosition + 1, utils.nowDate(), branchId]); - await sync_table.addBranchSync(branchId, sourceId); - }); + await sync_table.addBranchSync(branchId, sourceId); - res.send({ success: true }); -})); + return { success: true }; +} -router.put('/:branchId/expanded/:expanded', auth.checkApiAuth, wrap(async (req, res, next) => { +async function setExpanded(req) { const branchId = req.params.branchId; const expanded = req.params.expanded; - await sql.doInTransaction(async () => { - await sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]); + await sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]); + // we don't sync expanded label +} - // we don't sync expanded label - }); +async function deleteBranch(req) { + await notes.deleteNote(req.params.branchId, req.headers.source_id); +} - res.send({}); -})); - -router.delete('/:branchId', auth.checkApiAuth, wrap(async (req, res, next) => { - await sql.doInTransaction(async () => { - await notes.deleteNote(req.params.branchId, req.headers.source_id); - }); - - res.send({}); -})); - -module.exports = router; \ No newline at end of file +module.exports = { + moveBranchToParent, + moveBranchBeforeNote, + moveBranchAfterNote, + setExpanded, + deleteBranch +}; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index e2a5ebf43..764e54e23 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -32,6 +32,39 @@ const senderRoute = require('./api/sender'); const filesRoute = require('./api/file_upload'); const searchRoute = require('./api/search'); +const express = require('express'); +const router = express.Router(); +const auth = require('../services/auth'); +const cls = require('../services/cls'); +const sql = require('../services/sql'); + +function apiRoute(method, path, handler) { + router[method](path, auth.checkApiAuth, async (req, res, next) => { + try { + const resp = await cls.init(async () => { + return await sql.doInTransaction(async () => { + return await handler(req, res, next); + }); + }); + + if (Array.isArray(resp)) { + res.status(resp[0]).send(resp[1]); + } + else if (resp === undefined) { + res.status(200); + } + else { + res.status(200).send(resp); + } + } + catch (e) { + next(e); + } + }); +} + +const GET = 'get', POST = 'post', PUT = 'put', DELETE = 'delete'; + function register(app) { app.use('/', indexRoute); app.use('/login', loginRoute); @@ -39,9 +72,22 @@ function register(app) { app.use('/migration', migrationRoute); app.use('/setup', setupRoute); - app.use('/api/tree', treeApiRoute); - app.use('/api/notes', notesApiRoute); - app.use('/api/tree', treeChangesApiRoute); + apiRoute(GET, '/api/tree', treeApiRoute.getTree); + apiRoute(PUT, '/api/tree/:branchId/set-prefix', treeApiRoute.setPrefix); + + apiRoute(PUT, '/api/tree/:branchId/move-to/:parentNoteId', treeChangesApiRoute.moveBranchToParent); + apiRoute(PUT, '/api/tree/:branchId/move-before/:beforeBranchId', treeChangesApiRoute.moveBranchBeforeNote); + apiRoute(PUT, '/api/tree/:branchId/move-after/:afterBranchId', treeChangesApiRoute.moveBranchAfterNote); + apiRoute(PUT, '/api/tree/:branchId/expanded/:expanded', treeChangesApiRoute.setExpanded); + apiRoute(DELETE, '/api/tree/:branchId', treeChangesApiRoute.deleteBranch); + + apiRoute(GET, '/api/notes/:noteId', notesApiRoute.getNote); + apiRoute(PUT, '/api/notes/:noteId', notesApiRoute.updateNote); + apiRoute(POST, '/api/notes/:parentNoteId/children', notesApiRoute.createNote); + apiRoute(PUT, '/api/notes/:noteId/sort', notesApiRoute.sortNotes); + apiRoute(PUT, '/api/notes/:noteId/protect-sub-tree/:isProtected', notesApiRoute.protectBranch); + apiRoute(PUT, /\/api\/notes\/(.*)\/type\/(.*)\/mime\/(.*)/, notesApiRoute.setNoteTypeMime); + app.use('/api/notes', cloningApiRoute); app.use('/api', labelsRoute); app.use('/api/notes-revisions', noteRevisionsApiRoute); @@ -65,6 +111,7 @@ function register(app) { app.use('/api/sender', senderRoute); app.use('/api/files', filesRoute); app.use('/api/search', searchRoute); + app.use('', router); } module.exports = { diff --git a/src/services/tree.js b/src/services/tree.js index 9893a6d3b..59c066ce2 100644 --- a/src/services/tree.js +++ b/src/services/tree.js @@ -4,28 +4,24 @@ const sql = require('./sql'); const sync_table = require('./sync_table'); const protected_session = require('./protected_session'); -async function validateParentChild(res, parentNoteId, childNoteId, branchId = null) { +async function validateParentChild(parentNoteId, childNoteId, branchId = null) { const existing = await getExistingBranch(parentNoteId, childNoteId); if (existing && (branchId === null || existing.branchId !== branchId)) { - res.send({ + return { success: false, message: 'This note already exists in the target.' - }); - - return false; + }; } if (!await checkTreeCycle(parentNoteId, childNoteId)) { - res.send({ + return { success: false, message: 'Moving note here would create cycle.' - }); - - return false; + }; } - return true; + return { success: true }; } async function getExistingBranch(parentNoteId, childNoteId) {