db upgrades are now handled transparently in the background without bothering the user, closes #119

This commit is contained in:
azivner 2018-06-10 15:49:22 -04:00
parent 4c8eeb2e6f
commit 14c704d6db
11 changed files with 11 additions and 206 deletions

View file

@ -21,10 +21,7 @@ class Entity {
contentToHash += "|" + this[propertyName];
}
// this IF is to ease the migration from before hashed options, can be later removed
if (this.constructor.tableName !== 'options' || this.isSynced) {
this["hash"] = utils.hash(contentToHash).substr(0, 10);
}
this["hash"] = utils.hash(contentToHash).substr(0, 10);
}
async save() {

View file

@ -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;
});

View file

@ -5,7 +5,7 @@ import infoService from "./info.js";
function getHeaders() {
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();
}
catch(e) {}

View file

@ -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
};

View file

@ -1,9 +0,0 @@
"use strict";
function migrationPage(req, res) {
res.render('migration', {});
}
module.exports = {
migrationPage
};

View file

@ -1,6 +1,5 @@
const indexRoute = require('./index');
const loginRoute = require('./login');
const migrationRoute = require('./migration');
const setupRoute = require('./setup');
const multer = require('multer')();
@ -14,7 +13,6 @@ const noteRevisionsApiRoute = require('./api/note_revisions');
const recentChangesApiRoute = require('./api/recent_changes');
const optionsApiRoute = require('./api/options');
const passwordApiRoute = require('./api/password');
const migrationApiRoute = require('./api/migration');
const syncApiRoute = require('./api/sync');
const loginApiRoute = require('./api/login');
const eventLogRoute = require('./api/event_log');
@ -96,7 +94,6 @@ function register(app) {
route(GET, '/login', [], loginRoute.loginPage);
route(POST, '/login', [], loginRoute.login);
route(POST, '/logout', [auth.checkAuth], loginRoute.logout);
route(GET, '/migration', [auth.checkAuthForMigrationPage], migrationRoute.migrationPage);
route(GET, '/setup', [auth.checkAppNotInitialized], setupRoute.setupPage);
apiRoute(GET, '/api/tree', treeApiRoute.getTree);
@ -180,9 +177,6 @@ function register(app) {
apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes);
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);
// 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);

View file

@ -12,18 +12,6 @@ async function checkAuth(req, res, next) {
else if (!req.session.loggedIn && !utils.isElectron()) {
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 {
next();
}
@ -35,27 +23,12 @@ async function checkApiAuthOrElectron(req, res, next) {
if (!req.session.loggedIn && !utils.isElectron()) {
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
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) {
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) {
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
next();
}
}
module.exports = {
checkAuth,
checkAuthForMigrationPage,
checkApiAuth,
checkApiAuthForMigrationPage,
checkAppNotInitialized,
checkApiAuthOrElectron,
checkSenderToken

View file

@ -2,7 +2,6 @@ const repository = require('./repository');
const utils = require('./utils');
const dateUtils = require('./date_utils');
const appInfo = require('./app_info');
const Option = require('../entities/option');
async function getOption(name) {
const option = await repository.getOption(name);
@ -27,6 +26,9 @@ async function setOption(name, value) {
}
async function createOption(name, value, isSynced) {
// to avoid circular dependency, need to find better solution
const Option = require('../entities/option');
await new Option({
name: name,
value: value,

View file

@ -42,7 +42,10 @@ const dbReady = new Promise((resolve, reject) => {
}
if (!await isDbUpToDate()) {
return;
// avoiding circular dependency
const migrationService = require('./migration');
await migrationService.migrate();
}
resolve(db);

View file

@ -22,13 +22,6 @@ let syncServerCertificate = null;
async function sync() {
try {
await syncMutexService.doExclusively(async () => {
if (!await sqlInit.isDbUpToDate()) {
return {
success: false,
message: "DB not up to date"
};
}
const syncContext = await login();
await pushSync(syncContext);

View file

@ -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>