mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	better transaction handling with rollback on exception
This commit is contained in:
		| @@ -44,73 +44,70 @@ router.put('/:noteId', async (req, res, next) => { | |||||||
|  |  | ||||||
|     const history = await sql.getSingleResult("select id from notes_history where note_id = ? and date_modified_from >= ?", [noteId, historyCutoff]); |     const history = await sql.getSingleResult("select id from notes_history where note_id = ? and date_modified_from >= ?", [noteId, historyCutoff]); | ||||||
|  |  | ||||||
|     await sql.beginTransaction(); |     await sql.doInTransaction(async () => { | ||||||
|  |         if (history) { | ||||||
|  |             await sql.execute("update notes_history set note_title = ?, note_text = ?, encryption = ?, date_modified_to = ? where id = ?", [ | ||||||
|  |                 note['detail']['note_title'], | ||||||
|  |                 note['detail']['note_text'], | ||||||
|  |                 note['detail']['encryption'], | ||||||
|  |                 now, | ||||||
|  |                 history['id'] | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             await sql.execute("insert into notes_history (note_id, note_title, note_text, encryption, date_modified_from, date_modified_to) values (?, ?, ?, ?, ?, ?)", [ | ||||||
|  |                 noteId, | ||||||
|  |                 note['detail']['note_title'], | ||||||
|  |                 note['detail']['note_text'], | ||||||
|  |                 note['detail']['encryption'], | ||||||
|  |                 now, | ||||||
|  |                 now | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|     if (history) { |         if (note['detail']['note_title'] !== detail['note_title']) { | ||||||
|         await sql.execute("update notes_history set note_title = ?, note_text = ?, encryption = ?, date_modified_to = ? where id = ?", [ |             await sql.deleteRecentAudits(audit_category.UPDATE_TITLE, req, noteId); | ||||||
|  |             await sql.addAudit(audit_category.UPDATE_TITLE, req, noteId); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (note['detail']['note_text'] !== detail['note_text']) { | ||||||
|  |             await sql.deleteRecentAudits(audit_category.UPDATE_CONTENT, req, noteId); | ||||||
|  |             await sql.addAudit(audit_category.UPDATE_CONTENT, req, noteId); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (note['detail']['encryption'] !== detail['encryption']) { | ||||||
|  |             await sql.addAudit(audit_category.ENCRYPTION, req, noteId, detail['encryption'], note['detail']['encryption']); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         await sql.execute("update notes set note_title = ?, note_text = ?, encryption = ?, date_modified = ? where note_id = ?", [ | ||||||
|             note['detail']['note_title'], |             note['detail']['note_title'], | ||||||
|             note['detail']['note_text'], |             note['detail']['note_text'], | ||||||
|             note['detail']['encryption'], |             note['detail']['encryption'], | ||||||
|             now, |             now, | ||||||
|             history['id'] |             noteId]); | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         await sql.execute("insert into notes_history (note_id, note_title, note_text, encryption, date_modified_from, date_modified_to) values (?, ?, ?, ?, ?, ?)", [ |  | ||||||
|             noteId, |  | ||||||
|             note['detail']['note_title'], |  | ||||||
|             note['detail']['note_text'], |  | ||||||
|             note['detail']['encryption'], |  | ||||||
|             now, |  | ||||||
|             now |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (note['detail']['note_title'] !== detail['note_title']) { |         await sql.remove("images", noteId); | ||||||
|         await sql.deleteRecentAudits(audit_category.UPDATE_TITLE, req, noteId); |  | ||||||
|         await sql.addAudit(audit_category.UPDATE_TITLE, req, noteId); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (note['detail']['note_text'] !== detail['note_text']) { |         for (const img of note['images']) { | ||||||
|         await sql.deleteRecentAudits(audit_category.UPDATE_CONTENT, req, noteId); |             img['image_data'] = atob(img['image_data']); | ||||||
|         await sql.addAudit(audit_category.UPDATE_CONTENT, req, noteId); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (note['detail']['encryption'] !== detail['encryption']) { |             await sql.insert("images", img); | ||||||
|         await sql.addAudit(audit_category.ENCRYPTION, req, noteId, detail['encryption'], note['detail']['encryption']); |         } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     await sql.execute("update notes set note_title = ?, note_text = ?, encryption = ?, date_modified = ? where note_id = ?", [ |         await sql.remove("links", noteId); | ||||||
|         note['detail']['note_title'], |  | ||||||
|         note['detail']['note_text'], |  | ||||||
|         note['detail']['encryption'], |  | ||||||
|         now, |  | ||||||
|         noteId]); |  | ||||||
|  |  | ||||||
|     await sql.remove("images", noteId); |         for (const link in note['links']) { | ||||||
|  |             await sql.insert("links", link); | ||||||
|     for (const img of note['images']) { |         } | ||||||
|         img['image_data'] = atob(img['image_data']); |     }); | ||||||
|  |  | ||||||
|         await sql.insert("images", img); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     await sql.remove("links", noteId); |  | ||||||
|  |  | ||||||
|     for (const link in note['links']) |  | ||||||
|         await sql.insert("links", link); |  | ||||||
|  |  | ||||||
|     await sql.commit(); |  | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| router.delete('/:noteId', async (req, res, next) => { | router.delete('/:noteId', async (req, res, next) => { | ||||||
|     await sql.beginTransaction(); |     await sql.doInTransaction(async () => { | ||||||
|  |         await deleteNote(req.params.noteId, req); | ||||||
|     await deleteNote(req.params.noteId, req); |     }); | ||||||
|  |  | ||||||
|     await sql.commit(); |  | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
| }); | }); | ||||||
| @@ -165,33 +162,31 @@ router.post('/:parentNoteId/children', async (req, res, next) => { | |||||||
|         throw new ('Unknown target: ' + note['target']); |         throw new ('Unknown target: ' + note['target']); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     await sql.beginTransaction(); |     await sql.doInTransaction(async () => { | ||||||
|  |         await sql.addAudit(audit_category.CREATE_NOTE, req, noteId); | ||||||
|  |  | ||||||
|     await sql.addAudit(audit_category.CREATE_NOTE, req, noteId); |         const now = utils.nowTimestamp(); | ||||||
|  |  | ||||||
|     const now = utils.nowTimestamp(); |         await sql.insert("notes", { | ||||||
|  |             'note_id': noteId, | ||||||
|  |             'note_title': note['note_title'], | ||||||
|  |             'note_text': '', | ||||||
|  |             'note_clone_id': '', | ||||||
|  |             'date_created': now, | ||||||
|  |             'date_modified': now, | ||||||
|  |             'encryption': note['encryption'] | ||||||
|  |         }); | ||||||
|  |  | ||||||
|     await sql.insert("notes", { |         await sql.insert("notes_tree", { | ||||||
|         'note_id': noteId, |             'note_id': noteId, | ||||||
|         'note_title': note['note_title'], |             'note_pid': parentNoteId, | ||||||
|         'note_text': '', |             'note_pos': newNotePos, | ||||||
|         'note_clone_id': '', |             'is_expanded': 0, | ||||||
|         'date_created': now, |             'date_modified': utils.nowTimestamp(), | ||||||
|         'date_modified': now, |             'is_deleted': 0 | ||||||
|         'encryption': note['encryption'] |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     await sql.insert("notes_tree", { |  | ||||||
|         'note_id': noteId, |  | ||||||
|         'note_pid': parentNoteId, |  | ||||||
|         'note_pos': newNotePos, |  | ||||||
|         'is_expanded': 0, |  | ||||||
|         'date_modified': utils.nowTimestamp(), |  | ||||||
|         'is_deleted': 0 |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     await sql.commit(); |  | ||||||
|  |  | ||||||
|     res.send({ |     res.send({ | ||||||
|         'note_id': noteId |         'note_id': noteId | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -22,14 +22,12 @@ router.put('/:noteId/moveTo/:parentId', auth.checkApiAuth, async (req, res, next | |||||||
|  |  | ||||||
|     const now = utils.nowTimestamp(); |     const now = utils.nowTimestamp(); | ||||||
|  |  | ||||||
|     await sql.beginTransaction(); |     await sql.doInTransaction(async () => { | ||||||
|  |         await sql.execute("update notes_tree set note_pid = ?, note_pos = ?, date_modified = ? where note_id = ?", | ||||||
|  |             [parentId, newNotePos, now, noteId]); | ||||||
|  |  | ||||||
|     await sql.execute("update notes_tree set note_pid = ?, note_pos = ?, date_modified = ? where note_id = ?", |         await sql.addAudit(audit_category.CHANGE_PARENT, req, noteId, null, parentId); | ||||||
|         [parentId, newNotePos, now, noteId]); |     }); | ||||||
|  |  | ||||||
|     await sql.addAudit(audit_category.CHANGE_PARENT, req, noteId, null, parentId); |  | ||||||
|  |  | ||||||
|     await sql.commit(); |  | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
| }); | }); | ||||||
| @@ -43,16 +41,14 @@ router.put('/:noteId/moveBefore/:beforeNoteId', async (req, res, next) => { | |||||||
|     if (beforeNote) { |     if (beforeNote) { | ||||||
|         const now = utils.nowTimestamp(); |         const now = utils.nowTimestamp(); | ||||||
|  |  | ||||||
|         await sql.beginTransaction(); |         await sql.doInTransaction(async () => { | ||||||
|  |             await sql.execute("update notes_tree set note_pos = note_pos + 1, date_modified = ? where note_id = ?", [now, beforeNoteId]); | ||||||
|  |  | ||||||
|         await sql.execute("update notes_tree set note_pos = note_pos + 1, date_modified = ? where note_id = ?", [now, beforeNoteId]); |             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 = ?, date_modified = ? where note_id = ?", |             await sql.addAudit(audit_category.CHANGE_POSITION, req, noteId); | ||||||
|             [beforeNote['note_pid'], beforeNote['note_pos'], now, noteId]); |         }); | ||||||
|  |  | ||||||
|         await sql.addAudit(audit_category.CHANGE_POSITION, req, noteId); |  | ||||||
|  |  | ||||||
|         await sql.commit(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
| @@ -67,17 +63,15 @@ router.put('/:noteId/moveAfter/:afterNoteId', async (req, res, next) => { | |||||||
|     if (afterNote) { |     if (afterNote) { | ||||||
|         const now = utils.nowTimestamp(); |         const now = utils.nowTimestamp(); | ||||||
|  |  | ||||||
|         await sql.beginTransaction(); |         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']]); | ||||||
|  |  | ||||||
|         await sql.execute("update notes_tree set note_pos = note_pos + 1, date_modified = ? where note_pid = ? and note_pos > ? and is_deleted = 0", |             await sql.execute("update notes_tree set note_pid = ?, note_pos = ?, date_modified = ? where note_id = ?", | ||||||
|             [now, afterNote['note_pid'], afterNote['note_pos']]); |                 [afterNote['note_pid'], afterNote['note_pos'] + 1, now, noteId]); | ||||||
|  |  | ||||||
|         await sql.execute("update notes_tree set note_pid = ?, note_pos = ?, date_modified = ? where note_id = ?", |             await sql.addAudit(audit_category.CHANGE_POSITION, req, noteId); | ||||||
|             [afterNote['note_pid'], afterNote['note_pos'] + 1, now, noteId]); |         }); | ||||||
|  |  | ||||||
|         await sql.addAudit(audit_category.CHANGE_POSITION, req, noteId); |  | ||||||
|  |  | ||||||
|         await sql.commit(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
| @@ -88,9 +82,11 @@ router.put('/:noteId/expanded/:expanded', async (req, res, next) => { | |||||||
|     const expanded = req.params.expanded; |     const expanded = req.params.expanded; | ||||||
|     const now = utils.nowTimestamp(); |     const now = utils.nowTimestamp(); | ||||||
|  |  | ||||||
|     await sql.execute("update notes_tree set is_expanded = ?, date_modified = ? where note_id = ?", [expanded, now, noteId]); |     await sql.doInTransaction(async () => { | ||||||
|  |         await sql.execute("update notes_tree set is_expanded = ?, date_modified = ? where note_id = ?", [expanded, now, noteId]); | ||||||
|  |  | ||||||
|     // no audit here, not really important |         await sql.addAudit(audit_category.CHANGE_EXPANDED, req, noteId, null, expanded); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -27,13 +27,11 @@ router.post('/', async (req, res, next) => { | |||||||
|     if (ALLOWED_OPTIONS.includes(body['name'])) { |     if (ALLOWED_OPTIONS.includes(body['name'])) { | ||||||
|         const optionName = await sql.getOption(body['name']); |         const optionName = await sql.getOption(body['name']); | ||||||
|  |  | ||||||
|         await sql.beginTransaction(); |         await sql.doInTransaction(async () => { | ||||||
|  |             await sql.addAudit(audit_category.SETTINGS, req, null, optionName, body['value'], body['name']); | ||||||
|  |  | ||||||
|         await sql.addAudit(audit_category.SETTINGS, req, null, optionName, body['value'], body['name']); |             await sql.setOption(body['name'], body['value']); | ||||||
|  |         }); | ||||||
|         await sql.setOption(body['name'], body['value']); |  | ||||||
|  |  | ||||||
|         await sql.commit(); |  | ||||||
|  |  | ||||||
|         res.send({}); |         res.send({}); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ module.exports = { | |||||||
|     UPDATE_CONTENT: 'CONTENT', |     UPDATE_CONTENT: 'CONTENT', | ||||||
|     UPDATE_TITLE: 'TITLE', |     UPDATE_TITLE: 'TITLE', | ||||||
|     CHANGE_POSITION: 'POSITION', |     CHANGE_POSITION: 'POSITION', | ||||||
|  |     CHANGE_EXPANDED: 'EXPANDED', | ||||||
|     CREATE_NOTE: 'CREATE', |     CREATE_NOTE: 'CREATE', | ||||||
|     DELETE_NOTE: 'DELETE', |     DELETE_NOTE: 'DELETE', | ||||||
|     CHANGE_PARENT: 'PARENT', |     CHANGE_PARENT: 'PARENT', | ||||||
|   | |||||||
| @@ -55,15 +55,13 @@ async function changePassword(currentPassword, newPassword, req = null) { | |||||||
|  |  | ||||||
|     const newEncryptedDataKey = encrypt(decryptedDataKey); |     const newEncryptedDataKey = encrypt(decryptedDataKey); | ||||||
|  |  | ||||||
|     await sql.beginTransaction(); |     await sql.doInTransaction(async () => { | ||||||
|  |         await sql.setOption('encrypted_data_key', newEncryptedDataKey); | ||||||
|  |  | ||||||
|     await sql.setOption('encrypted_data_key', newEncryptedDataKey); |         await sql.setOption('password_verification_hash', newPasswordVerificationKey); | ||||||
|  |  | ||||||
|     await sql.setOption('password_verification_hash', newPasswordVerificationKey); |         await sql.addAudit(audit_category.CHANGE_PASSWORD, req); | ||||||
|  |     }); | ||||||
|     await sql.addAudit(audit_category.CHANGE_PASSWORD, req); |  | ||||||
|  |  | ||||||
|     await sql.commit(); |  | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         'success': true, |         'success': true, | ||||||
|   | |||||||
| @@ -42,13 +42,11 @@ async function migrate() { | |||||||
|         try { |         try { | ||||||
|             log.info("Attempting migration to version " + mig.dbVersion + " with script: " + migrationSql); |             log.info("Attempting migration to version " + mig.dbVersion + " with script: " + migrationSql); | ||||||
|  |  | ||||||
|             await sql.beginTransaction(); |             await sql.doInTransaction(async () => { | ||||||
|  |                 await sql.executeScript(migrationSql); | ||||||
|  |  | ||||||
|             await sql.executeScript(migrationSql); |                 await sql.setOption("db_version", mig.dbVersion); | ||||||
|  |             }); | ||||||
|             await sql.setOption("db_version", mig.dbVersion); |  | ||||||
|  |  | ||||||
|             await sql.commit(); |  | ||||||
|  |  | ||||||
|             log.info("Migration to version " + mig.dbVersion + " has been successful."); |             log.info("Migration to version " + mig.dbVersion + " has been successful."); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -109,6 +109,21 @@ async function deleteRecentAudits(category, req, noteId) { | |||||||
|             [category, browserId, noteId, deleteCutoff]) |             [category, browserId, noteId, deleteCutoff]) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function doInTransaction(func) { | ||||||
|  |     try { | ||||||
|  |         await beginTransaction(); | ||||||
|  |  | ||||||
|  |         await func(); | ||||||
|  |  | ||||||
|  |         await commit(); | ||||||
|  |     } | ||||||
|  |     catch (e) { | ||||||
|  |         log.error("Error executing transaction, executing rollback. Inner exception: " + e.stack); | ||||||
|  |  | ||||||
|  |         await rollback(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     insert, |     insert, | ||||||
|     getSingleResult, |     getSingleResult, | ||||||
| @@ -118,10 +133,8 @@ module.exports = { | |||||||
|     executeScript, |     executeScript, | ||||||
|     getOption, |     getOption, | ||||||
|     setOption, |     setOption, | ||||||
|     beginTransaction, |  | ||||||
|     commit, |  | ||||||
|     rollback, |  | ||||||
|     addAudit, |     addAudit, | ||||||
|     deleteRecentAudits, |     deleteRecentAudits, | ||||||
|     remove |     remove, | ||||||
|  |     doInTransaction | ||||||
| }; | }; | ||||||
| @@ -34,41 +34,37 @@ async function pullSync(cookieJar, syncLog) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|         await sql.beginTransaction(); |         await sql.doInTransaction(async () => { | ||||||
|  |             await putChanged(resp, syncLog); | ||||||
|  |  | ||||||
|         await putChanged(resp, syncLog); |             for (const noteId of resp.notes) { | ||||||
|  |                 let note; | ||||||
|  |  | ||||||
|         for (const noteId of resp.notes) { |                 try { | ||||||
|             let note; |                     note = await rp({ | ||||||
|  |                         uri: SYNC_SERVER + "/api/sync/note/" + noteId + "/" + lastSyncedPull, | ||||||
|  |                         headers: { | ||||||
|  |                             auth: 'sync' | ||||||
|  |                         }, | ||||||
|  |                         json: true, | ||||||
|  |                         jar: cookieJar | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |                 catch (e) { | ||||||
|  |                     throw new Error("Can't pull note " + noteId + ", inner exception: " + e.stack); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|             try { |                 await putNote(note, syncLog); | ||||||
|                 note = await rp({ |  | ||||||
|                     uri: SYNC_SERVER + "/api/sync/note/" + noteId + "/" + lastSyncedPull, |  | ||||||
|                     headers: { |  | ||||||
|                         auth: 'sync' |  | ||||||
|                     }, |  | ||||||
|                     json: true, |  | ||||||
|                     jar: cookieJar |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|             catch (e) { |  | ||||||
|                 throw new Error("Can't pull note " + noteId + ", inner exception: " + e.stack); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             await putNote(note, syncLog); |             if (resp.notes.length > 0) { | ||||||
|         } |                 await sql.addAudit(audit_category.SYNC); | ||||||
|  |             } | ||||||
|  |  | ||||||
|         if (resp.notes.length > 0) { |             await sql.setOption('last_synced_pull', resp.syncTimestamp); | ||||||
|             await sql.addAudit(audit_category.SYNC); |         }); | ||||||
|         } |  | ||||||
|  |  | ||||||
|         await sql.setOption('last_synced_pull', resp.syncTimestamp); |  | ||||||
|  |  | ||||||
|         await sql.commit(); |  | ||||||
|     } |     } | ||||||
|     catch (e) { |     catch (e) { | ||||||
|         await sql.rollback(); |  | ||||||
|  |  | ||||||
|         throw e; |         throw e; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user