From a7f95e95e98c009d4463e3bd47af51156814fbf3 Mon Sep 17 00:00:00 2001 From: azivner Date: Thu, 2 Nov 2017 22:55:22 -0400 Subject: [PATCH] proper syncing of note positions --- routes/api/notes_move.js | 30 ++++++++++++++++-------------- routes/api/sync.js | 15 +++++++++++++++ services/audit_category.js | 1 + services/sql.js | 19 +++++++++++++++++++ services/sync.js | 21 +++++++++++++++++++++ 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/routes/api/notes_move.js b/routes/api/notes_move.js index 295bbc15a..c921234f8 100644 --- a/routes/api/notes_move.js +++ b/routes/api/notes_move.js @@ -40,16 +40,17 @@ router.put('/:noteId/moveBefore/:beforeNoteId', async (req, res, next) => { const beforeNote = await sql.getSingleResult("select * from notes_tree where note_id = ?", [beforeNoteId]); if (beforeNote) { - const now = utils.nowTimestamp(); - await sql.doInTransaction(async () => { - await sql.execute("update notes_tree set note_pos = note_pos + 1, date_modified = ? where note_id = ?", [now, beforeNoteId]); + // we don't change date_modified so other changes are prioritized in case of conflict + await sql.execute("update notes_tree set note_pos = note_pos + 1 where note_pid = ? and note_pos >= ? and is_deleted = 0", + [beforeNote['note_pid'], beforeNote['note_pos']]); - await sql.execute("update notes_tree set note_pid = ?, note_pos = ?, date_modified = ? where note_id = ?", - [beforeNote['note_pid'], beforeNote['note_pos'], now, noteId]); + await sql.execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", + [beforeNote['note_pid'], beforeNote['note_pos'], noteId]); await sql.addNoteTreeSync(noteId); - await sql.addAudit(audit_category.CHANGE_POSITION, req, noteId); + await sql.addNoteReorderingSync(beforeNote['note_pid']); + await sql.addAudit(audit_category.CHANGE_POSITION, req, beforeNote['note_pid']); }); } @@ -63,17 +64,17 @@ router.put('/:noteId/moveAfter/:afterNoteId', async (req, res, next) => { const afterNote = await sql.getSingleResult("select * from notes_tree where note_id = ?", [afterNoteId]); if (afterNote) { - const now = utils.nowTimestamp(); - await sql.doInTransaction(async () => { - await sql.execute("update notes_tree set note_pos = note_pos + 1, date_modified = ? where note_pid = ? and note_pos > ? and is_deleted = 0", - [now, afterNote['note_pid'], afterNote['note_pos']]); + // we don't change date_modified so other changes are prioritized in case of conflict + await sql.execute("update notes_tree set note_pos = note_pos + 1 where note_pid = ? and note_pos > ? and is_deleted = 0", + [afterNote['note_pid'], afterNote['note_pos']]); - await sql.execute("update notes_tree set note_pid = ?, note_pos = ?, date_modified = ? where note_id = ?", - [afterNote['note_pid'], afterNote['note_pos'] + 1, now, noteId]); + await sql.execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", + [afterNote['note_pid'], afterNote['note_pos'] + 1, noteId]); await sql.addNoteTreeSync(noteId); - await sql.addAudit(audit_category.CHANGE_POSITION, req, noteId); + await sql.addNoteReorderingSync(afterNote['note_pid']); + await sql.addAudit(audit_category.CHANGE_POSITION, req, afterNote['note_pid']); }); } @@ -86,7 +87,8 @@ router.put('/:noteId/expanded/:expanded', async (req, res, next) => { const now = utils.nowTimestamp(); await sql.doInTransaction(async () => { - await sql.execute("update notes_tree set is_expanded = ?, date_modified = ? where note_id = ?", [expanded, now, noteId]); + // we don't change date_modified so other changes are prioritized in case of conflict + await sql.execute("update notes_tree set is_expanded = ? where note_id = ?", [expanded, noteId]); await sql.addNoteTreeSync(noteId); await sql.addAudit(audit_category.CHANGE_EXPANDED, req, noteId, null, expanded); diff --git a/routes/api/sync.js b/routes/api/sync.js index c7665f6b0..adcb2c2c4 100644 --- a/routes/api/sync.js +++ b/routes/api/sync.js @@ -55,6 +55,15 @@ router.get('/options/:optName', auth.checkApiAuth, async (req, res, next) => { } }); +router.get('/notes_reordering/:noteParentId', auth.checkApiAuth, async (req, res, next) => { + const noteParentId = req.params.noteParentId; + + res.send({ + note_pid: noteParentId, + ordering: await sql.getMap("SELECT note_id, note_pos FROM notes_tree WHERE note_pid = ?", [noteParentId]) + }); +}); + router.put('/notes', auth.checkApiAuth, async (req, res, next) => { await sync.updateNote(req.body.entity, req.body.links, req.body.sourceId); @@ -73,6 +82,12 @@ router.put('/notes_history', auth.checkApiAuth, async (req, res, next) => { res.send({}); }); +router.put('/notes_reordering', auth.checkApiAuth, async (req, res, next) => { + await sync.updateNoteReordering(req.body.entity, req.body.sourceId); + + res.send({}); +}); + router.put('/options', auth.checkApiAuth, async (req, res, next) => { await sync.updateOptions(req.body.entity, req.body.sourceId); diff --git a/services/audit_category.js b/services/audit_category.js index cc939969f..e438a2bc6 100644 --- a/services/audit_category.js +++ b/services/audit_category.js @@ -3,6 +3,7 @@ module.exports = { UPDATE_CONTENT: 'CONTENT', UPDATE_TITLE: 'TITLE', + // associated noteId is parent of notes where position changes happened CHANGE_POSITION: 'POSITION', CHANGE_EXPANDED: 'EXPANDED', CREATE_NOTE: 'CREATE', diff --git a/services/sql.js b/services/sql.js index db2a4a4f3..33ad1a7a3 100644 --- a/services/sql.js +++ b/services/sql.js @@ -62,6 +62,19 @@ async function getResults(query, params = []) { return await wrap(async () => db.all(query, ...params)); } +async function getMap(query, params = []) { + const map = {}; + const results = await getResults(query, params); + + for (const row of results) { + const keys = Object.keys(row); + + map[row[keys[0]]] = row[keys[1]]; + } + + return map; +} + async function getFlattenedResults(key, query, params = []) { const list = []; const result = await getResults(query, params); @@ -123,6 +136,10 @@ async function addNoteTreeSync(noteId, sourceId) { await addEntitySync("notes_tree", noteId, sourceId) } +async function addNoteReorderingSync(noteId, sourceId) { + await addEntitySync("notes_reordering", noteId, sourceId) +} + async function addNoteHistorySync(noteHistoryId, sourceId) { await addEntitySync("notes_history", noteHistoryId, sourceId); } @@ -180,6 +197,7 @@ module.exports = { getSingleResult, getSingleResultOrNull, getResults, + getMap, getFlattenedResults, execute, executeScript, @@ -190,6 +208,7 @@ module.exports = { doInTransaction, addNoteSync, addNoteTreeSync, + addNoteReorderingSync, addNoteHistorySync, addOptionsSync }; \ No newline at end of file diff --git a/services/sync.js b/services/sync.js index f571e99ca..63c5f9723 100644 --- a/services/sync.js +++ b/services/sync.js @@ -60,6 +60,9 @@ async function pullSync(syncContext, syncLog) { else if (sync.entity_name === 'notes_history') { await updateNoteHistory(resp, syncContext.sourceId, syncLog); } + else if (sync.entity_name === 'notes_reordering') { + await updateNoteReordering(resp, syncContext.sourceId, syncLog); + } else if (sync.entity_name === 'options') { await updateOptions(resp, syncContext.sourceId, syncLog); } @@ -110,6 +113,12 @@ async function readAndPushEntity(sync, syncLog, syncContext) { else if (sync.entity_name === 'notes_history') { entity = await sql.getSingleResult('SELECT * FROM notes_history WHERE note_history_id = ?', [sync.entity_id]); } + else if (sync.entity_name === 'notes_reordering') { + entity = { + note_pid: sync.entity_id, + ordering: await sql.getMap('SELECT note_id, note_pos FROM notes_tree WHERE note_pid = ?', [sync.entity_id]) + }; + } else if (sync.entity_name === 'options') { entity = await sql.getSingleResult('SELECT * FROM options WHERE opt_name = ?', [sync.entity_id]); } @@ -306,6 +315,17 @@ async function updateNoteHistory(entity, sourceId, syncLog) { } } +async function updateNoteReordering(entity, sourceId, syncLog) { + await sql.doInTransaction(async () => { + Object.keys(entity.ordering).forEach(async key => { + await sql.execute("UPDATE notes_tree SET note_pos = ? WHERE note_id = ?", [entity.ordering[key], key]); + }); + + await sql.addNoteReorderingSync(entity.note_pid, sourceId); + await sql.addSyncAudit(audit_category.CHANGE_POSITION, sourceId, entity.note_pid); + }); +} + async function updateOptions(entity, sourceId, syncLog) { if (!options.SYNCED_OPTIONS.includes(entity.opt_name)) { return; @@ -344,6 +364,7 @@ module.exports = { updateNote, updateNoteTree, updateNoteHistory, + updateNoteReordering, updateOptions, isSyncSetup }; \ No newline at end of file