mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 21:11:30 +08:00 
			
		
		
		
	db upgrades are now handled transparently in the background without bothering the user, closes #119
This commit is contained in:
		
							parent
							
								
									4c8eeb2e6f
								
							
						
					
					
						commit
						14c704d6db
					
				| @ -21,10 +21,7 @@ class Entity { | |||||||
|             contentToHash += "|" + this[propertyName]; |             contentToHash += "|" + this[propertyName]; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // this IF is to ease the migration from before hashed options, can be later removed
 |         this["hash"] = utils.hash(contentToHash).substr(0, 10); | ||||||
|         if (this.constructor.tableName !== 'options' || this.isSynced) { |  | ||||||
|             this["hash"] = utils.hash(contentToHash).substr(0, 10); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async save() { |     async save() { | ||||||
|  | |||||||
| @ -1,46 +0,0 @@ | |||||||
| import server from './services/server.js'; |  | ||||||
| 
 |  | ||||||
| $(document).ready(async () => { |  | ||||||
|     const {appDbVersion, dbVersion} = await server.get('migration'); |  | ||||||
| 
 |  | ||||||
|     console.log("HI", {appDbVersion, dbVersion}); |  | ||||||
| 
 |  | ||||||
|     if (appDbVersion === dbVersion) { |  | ||||||
|         $("#up-to-date").show(); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         $("#need-to-migrate").show(); |  | ||||||
| 
 |  | ||||||
|         $("#app-db-version").html(appDbVersion); |  | ||||||
|         $("#db-version").html(dbVersion); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| $("#run-migration").click(async () => { |  | ||||||
|     $("#run-migration").prop("disabled", true); |  | ||||||
| 
 |  | ||||||
|     $("#migration-result").show(); |  | ||||||
| 
 |  | ||||||
|     const result = await server.post('migration'); |  | ||||||
| 
 |  | ||||||
|     for (const migration of result.migrations) { |  | ||||||
|         const row = $('<tr>') |  | ||||||
|             .append($('<td>').html(migration.dbVersion)) |  | ||||||
|             .append($('<td>').html(migration.name)) |  | ||||||
|             .append($('<td>').html(migration.success ? 'Yes' : 'No')) |  | ||||||
|             .append($('<td>').html(migration.success ? 'N/A' : migration.error)); |  | ||||||
| 
 |  | ||||||
|         if (!migration.success) { |  | ||||||
|             row.addClass("danger"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $("#migration-table").append(row); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| // copy of this shortcut to be able to debug migration problems
 |  | ||||||
| $(document).bind('keydown', 'ctrl+shift+i', () => { |  | ||||||
|     require('electron').remote.getCurrentWindow().toggleDevTools(); |  | ||||||
| 
 |  | ||||||
|     return false; |  | ||||||
| }); |  | ||||||
| @ -5,7 +5,7 @@ import infoService from "./info.js"; | |||||||
| function getHeaders() { | function getHeaders() { | ||||||
|     let protectedSessionId = null; |     let protectedSessionId = null; | ||||||
| 
 | 
 | ||||||
|     try { // this is because protected session might not be declared in some cases - like when it's included in migration page
 |     try { // this is because protected session might not be declared in some cases
 | ||||||
|         protectedSessionId = protectedSessionHolder.getProtectedSessionId(); |         protectedSessionId = protectedSessionHolder.getProtectedSessionId(); | ||||||
|     } |     } | ||||||
|     catch(e) {} |     catch(e) {} | ||||||
|  | |||||||
| @ -1,25 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| const optionService = require('../../services/options'); |  | ||||||
| const migrationService = require('../../services/migration'); |  | ||||||
| const appInfo = require('../../services/app_info'); |  | ||||||
| 
 |  | ||||||
| async function getMigrationInfo() { |  | ||||||
|     return { |  | ||||||
|         dbVersion: parseInt(await optionService.getOption('dbVersion')), |  | ||||||
|         appDbVersion: appInfo.dbVersion |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function executeMigration() { |  | ||||||
|     const migrations = await migrationService.migrate(); |  | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|         migrations: migrations |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
|     getMigrationInfo, |  | ||||||
|     executeMigration |  | ||||||
| }; |  | ||||||
| @ -1,9 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| function migrationPage(req, res) { |  | ||||||
|     res.render('migration', {}); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
|     migrationPage |  | ||||||
| }; |  | ||||||
| @ -1,6 +1,5 @@ | |||||||
| const indexRoute = require('./index'); | const indexRoute = require('./index'); | ||||||
| const loginRoute = require('./login'); | const loginRoute = require('./login'); | ||||||
| const migrationRoute = require('./migration'); |  | ||||||
| const setupRoute = require('./setup'); | const setupRoute = require('./setup'); | ||||||
| const multer = require('multer')(); | const multer = require('multer')(); | ||||||
| 
 | 
 | ||||||
| @ -14,7 +13,6 @@ const noteRevisionsApiRoute = require('./api/note_revisions'); | |||||||
| const recentChangesApiRoute = require('./api/recent_changes'); | const recentChangesApiRoute = require('./api/recent_changes'); | ||||||
| const optionsApiRoute = require('./api/options'); | const optionsApiRoute = require('./api/options'); | ||||||
| const passwordApiRoute = require('./api/password'); | const passwordApiRoute = require('./api/password'); | ||||||
| const migrationApiRoute = require('./api/migration'); |  | ||||||
| const syncApiRoute = require('./api/sync'); | const syncApiRoute = require('./api/sync'); | ||||||
| const loginApiRoute = require('./api/login'); | const loginApiRoute = require('./api/login'); | ||||||
| const eventLogRoute = require('./api/event_log'); | const eventLogRoute = require('./api/event_log'); | ||||||
| @ -96,7 +94,6 @@ function register(app) { | |||||||
|     route(GET, '/login', [], loginRoute.loginPage); |     route(GET, '/login', [], loginRoute.loginPage); | ||||||
|     route(POST, '/login', [], loginRoute.login); |     route(POST, '/login', [], loginRoute.login); | ||||||
|     route(POST, '/logout', [auth.checkAuth], loginRoute.logout); |     route(POST, '/logout', [auth.checkAuth], loginRoute.logout); | ||||||
|     route(GET, '/migration', [auth.checkAuthForMigrationPage], migrationRoute.migrationPage); |  | ||||||
|     route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage); |     route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage); | ||||||
| 
 | 
 | ||||||
|     apiRoute(GET, '/api/tree', treeApiRoute.getTree); |     apiRoute(GET, '/api/tree', treeApiRoute.getTree); | ||||||
| @ -180,9 +177,6 @@ function register(app) { | |||||||
|     apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes); |     apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes); | ||||||
|     apiRoute(POST, '/api/search/:searchString', searchRoute.saveSearchToNote); |     apiRoute(POST, '/api/search/:searchString', searchRoute.saveSearchToNote); | ||||||
| 
 | 
 | ||||||
|     route(GET, '/api/migration', [auth.checkApiAuthForMigrationPage], migrationApiRoute.getMigrationInfo, apiResultHandler); |  | ||||||
|     route(POST, '/api/migration', [auth.checkApiAuthForMigrationPage], migrationApiRoute.executeMigration, apiResultHandler); |  | ||||||
| 
 |  | ||||||
|     route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); |     route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); | ||||||
|     // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)
 |     // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)
 | ||||||
|     apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); |     apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); | ||||||
|  | |||||||
| @ -12,18 +12,6 @@ async function checkAuth(req, res, next) { | |||||||
|     else if (!req.session.loggedIn && !utils.isElectron()) { |     else if (!req.session.loggedIn && !utils.isElectron()) { | ||||||
|         res.redirect("login"); |         res.redirect("login"); | ||||||
|     } |     } | ||||||
|     else if (!await sqlInit.isDbUpToDate()) { |  | ||||||
|         res.redirect("migration"); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function checkAuthForMigrationPage(req, res, next) { |  | ||||||
|     if (!req.session.loggedIn && !utils.isElectron()) { |  | ||||||
|         res.redirect("login"); |  | ||||||
|     } |  | ||||||
|     else { |     else { | ||||||
|         next(); |         next(); | ||||||
|     } |     } | ||||||
| @ -35,27 +23,12 @@ async function checkApiAuthOrElectron(req, res, next) { | |||||||
|     if (!req.session.loggedIn && !utils.isElectron()) { |     if (!req.session.loggedIn && !utils.isElectron()) { | ||||||
|         res.status(401).send("Not authorized"); |         res.status(401).send("Not authorized"); | ||||||
|     } |     } | ||||||
|     else if (await sqlInit.isDbUpToDate()) { |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
|     else { |     else { | ||||||
|         res.status(409).send("Mismatched app versions"); // need better response than that
 |         next(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function checkApiAuth(req, res, next) { | async function checkApiAuth(req, res, next) { | ||||||
|     if (!req.session.loggedIn) { |  | ||||||
|         res.status(401).send("Not authorized"); |  | ||||||
|     } |  | ||||||
|     else if (await sqlInit.isDbUpToDate()) { |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         res.status(409).send("Mismatched app versions"); // need better response than that
 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function checkApiAuthForMigrationPage(req, res, next) { |  | ||||||
|     if (!req.session.loggedIn) { |     if (!req.session.loggedIn) { | ||||||
|         res.status(401).send("Not authorized"); |         res.status(401).send("Not authorized"); | ||||||
|     } |     } | ||||||
| @ -79,19 +52,14 @@ async function checkSenderToken(req, res, next) { | |||||||
|     if (await sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) { |     if (await sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) { | ||||||
|         res.status(401).send("Not authorized"); |         res.status(401).send("Not authorized"); | ||||||
|     } |     } | ||||||
|     else if (await sqlInit.isDbUpToDate()) { |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
|     else { |     else { | ||||||
|         res.status(409).send("Mismatched app versions"); // need better response than that
 |         next(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     checkAuth, |     checkAuth, | ||||||
|     checkAuthForMigrationPage, |  | ||||||
|     checkApiAuth, |     checkApiAuth, | ||||||
|     checkApiAuthForMigrationPage, |  | ||||||
|     checkAppNotInitialized, |     checkAppNotInitialized, | ||||||
|     checkApiAuthOrElectron, |     checkApiAuthOrElectron, | ||||||
|     checkSenderToken |     checkSenderToken | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ const repository = require('./repository'); | |||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const dateUtils = require('./date_utils'); | const dateUtils = require('./date_utils'); | ||||||
| const appInfo = require('./app_info'); | const appInfo = require('./app_info'); | ||||||
| const Option = require('../entities/option'); |  | ||||||
| 
 | 
 | ||||||
| async function getOption(name) { | async function getOption(name) { | ||||||
|     const option = await repository.getOption(name); |     const option = await repository.getOption(name); | ||||||
| @ -27,6 +26,9 @@ async function setOption(name, value) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function createOption(name, value, isSynced) { | async function createOption(name, value, isSynced) { | ||||||
|  |     // to avoid circular dependency, need to find better solution
 | ||||||
|  |     const Option = require('../entities/option'); | ||||||
|  | 
 | ||||||
|     await new Option({ |     await new Option({ | ||||||
|         name: name, |         name: name, | ||||||
|         value: value, |         value: value, | ||||||
|  | |||||||
| @ -42,7 +42,10 @@ const dbReady = new Promise((resolve, reject) => { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!await isDbUpToDate()) { |         if (!await isDbUpToDate()) { | ||||||
|             return; |             // avoiding circular dependency
 | ||||||
|  |             const migrationService = require('./migration'); | ||||||
|  | 
 | ||||||
|  |             await migrationService.migrate(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         resolve(db); |         resolve(db); | ||||||
|  | |||||||
| @ -22,13 +22,6 @@ let syncServerCertificate = null; | |||||||
| async function sync() { | async function sync() { | ||||||
|     try { |     try { | ||||||
|         await syncMutexService.doExclusively(async () => { |         await syncMutexService.doExclusively(async () => { | ||||||
|             if (!await sqlInit.isDbUpToDate()) { |  | ||||||
|                 return { |  | ||||||
|                     success: false, |  | ||||||
|                     message: "DB not up to date" |  | ||||||
|                 }; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             const syncContext = await login(); |             const syncContext = await login(); | ||||||
| 
 | 
 | ||||||
|             await pushSync(syncContext); |             await pushSync(syncContext); | ||||||
|  | |||||||
| @ -1,72 +0,0 @@ | |||||||
| <!DOCTYPE html> |  | ||||||
| <html lang="en"> |  | ||||||
|   <head> |  | ||||||
|     <meta charset="utf-8"> |  | ||||||
|     <title>Migration</title> |  | ||||||
|   </head> |  | ||||||
|   <body> |  | ||||||
|     <div style="width: 800px; margin: auto;"> |  | ||||||
|       <h1>Migration</h1> |  | ||||||
| 
 |  | ||||||
|       <div id="up-to-date" style="display:none;"> |  | ||||||
|         <p>Your database is up-to-date with the application.</p> |  | ||||||
| 
 |  | ||||||
|         <a href="/" class="btn btn-success">Continue to app</a> |  | ||||||
|       </div> |  | ||||||
| 
 |  | ||||||
|       <div id="need-to-migrate" style="display:none;"> |  | ||||||
|         <p>Your database needs to be migrated to new version before you can use the application again. |  | ||||||
|         Database will be backed up before migration in case of something going wrong.</p> |  | ||||||
| 
 |  | ||||||
|         <table class="table table-bordered" style="width: 200px;"> |  | ||||||
|           <tr> |  | ||||||
|             <th>Application version:</th> |  | ||||||
|             <td id="app-db-version" style="text-align: right;"></td> |  | ||||||
|           <tr> |  | ||||||
|             <th>Database version:</th> |  | ||||||
|             <td id="db-version" style="text-align: right;"></td> |  | ||||||
|           </tr> |  | ||||||
|         </table> |  | ||||||
| 
 |  | ||||||
|         <button class="btn btn-warning" id="run-migration">Run migration</button> |  | ||||||
|       </div> |  | ||||||
| 
 |  | ||||||
|       <div id="migration-result" style="display:none;"> |  | ||||||
|         <h2>Migration result</h2> |  | ||||||
| 
 |  | ||||||
|         <table id="migration-table" class="table"> |  | ||||||
|           <tr> |  | ||||||
|             <th>Database version</th> |  | ||||||
|             <th>Name</th> |  | ||||||
|             <th>Success</th> |  | ||||||
|             <th>Error</th> |  | ||||||
|           </tr> |  | ||||||
|         </table> |  | ||||||
| 
 |  | ||||||
|         <a href="/" class="btn btn-success">Continue to app</a> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|     <script type="text/javascript"> |  | ||||||
|       const baseApiUrl = 'api/'; |  | ||||||
|     </script> |  | ||||||
| 
 |  | ||||||
|     <!-- Required for correct loading of scripts in Electron --> |  | ||||||
|     <script> |  | ||||||
|         if (typeof module === 'object') { |  | ||||||
|             window.module = module; module = undefined; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const glob = { |  | ||||||
|             sourceId: '' |  | ||||||
|         }; |  | ||||||
|     </script> |  | ||||||
| 
 |  | ||||||
|     <script src="libraries/jquery.min.js"></script> |  | ||||||
| 
 |  | ||||||
|     <link href="libraries/bootstrap/css/bootstrap.css" rel="stylesheet"> |  | ||||||
|     <script src="libraries/bootstrap/js/bootstrap.js"></script> |  | ||||||
| 
 |  | ||||||
|     <script src="javascripts/migration.js" type="module"></script> |  | ||||||
|   </body> |  | ||||||
| </html> |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 azivner
						azivner