From 013714cb5c446dc80d65beb99f9fb93ba93aa50b Mon Sep 17 00:00:00 2001 From: azivner Date: Tue, 24 Jul 2018 08:12:36 +0200 Subject: [PATCH] #98, new option "initialized" which indicates if setup has been finished --- .../0103__add_initialized_option.sql | 2 + src/app.js | 2 +- src/public/javascripts/setup.js | 15 +++-- src/routes/api/setup.js | 45 +------------ src/routes/api/sync.js | 5 +- src/routes/setup.js | 22 ++++++- src/services/app_info.js | 2 +- src/services/options_init.js | 4 +- src/services/setup.js | 66 +++++++++++++++++++ src/services/sql_init.js | 23 +++++-- src/views/setup.ejs | 1 + 11 files changed, 130 insertions(+), 57 deletions(-) create mode 100644 db/migrations/0103__add_initialized_option.sql create mode 100644 src/services/setup.js diff --git a/db/migrations/0103__add_initialized_option.sql b/db/migrations/0103__add_initialized_option.sql new file mode 100644 index 000000000..86d0e6768 --- /dev/null +++ b/db/migrations/0103__add_initialized_option.sql @@ -0,0 +1,2 @@ +INSERT INTO options (name, value, dateCreated, dateModified, isSynced) +VALUES ('initialized', 'true', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0); \ No newline at end of file diff --git a/src/app.js b/src/app.js index c8ae10769..da3e27d1b 100644 --- a/src/app.js +++ b/src/app.js @@ -47,7 +47,7 @@ const sessionParser = session({ cookie: { // path: "/", httpOnly: true, - maxAge: 1800000 + maxAge: 24 * 60 * 60 * 1000 // in milliseconds }, store: new FileStore({ ttl: 30 * 24 * 3600, diff --git a/src/public/javascripts/setup.js b/src/public/javascripts/setup.js index 1cf530e20..b10325a51 100644 --- a/src/public/javascripts/setup.js +++ b/src/public/javascripts/setup.js @@ -1,7 +1,11 @@ import utils from "./services/utils.js"; function SetupModel() { - this.step = ko.observable("setup-type"); + if (syncInProgress) { + setInterval(checkOutstandingSyncs, 1000); + } + + this.step = ko.observable(syncInProgress ? "sync-in-progress" : "setup-type"); this.setupType = ko.observable(); this.setupNewDocument = ko.observable(false); @@ -93,8 +97,6 @@ function SetupModel() { if (resp.result === 'success') { this.step('sync-in-progress'); - checkOutstandingSyncs(); - setInterval(checkOutstandingSyncs, 1000); } else { @@ -105,7 +107,12 @@ function SetupModel() { } async function checkOutstandingSyncs() { - const stats = await $.get('/api/sync/stats'); + const { stats, initialized } = await $.get('/api/sync/stats'); + + if (initialized) { + window.location.replace("/"); + } + const totalOutstandingSyncs = stats.outstandingPushes + stats.outstandingPulls; $("#outstanding-syncs").html(totalOutstandingSyncs); diff --git a/src/routes/api/setup.js b/src/routes/api/setup.js index 48c7c5cd4..cbc0f5e7d 100644 --- a/src/routes/api/setup.js +++ b/src/routes/api/setup.js @@ -1,11 +1,7 @@ "use strict"; const sqlInit = require('../../services/sql_init'); -const sql = require('../../services/sql'); -const rp = require('request-promise'); -const Option = require('../../entities/option'); -const syncService = require('../../services/sync'); -const log = require('../../services/log'); +const setupService = require('../../services/setup'); async function setupNewDocument(req) { const { username, password } = req.body; @@ -16,44 +12,7 @@ async function setupNewDocument(req) { async function setupSyncFromServer(req) { const { serverAddress, username, password } = req.body; - try { - log.info("Getting document options from sync server."); - - // response is expected to contain documentId and documentSecret options - const options = await rp.get({ - uri: serverAddress + '/api/sync/document', - auth: { - 'user': username, - 'pass': password - }, - json: true - }); - - log.info("Creating database for sync"); - - await sql.transactional(async () => { - await sqlInit.createDatabaseForSync(serverAddress); - - for (const opt of options) { - await new Option(opt).save(); - } - }); - - log.info("Triggering sync."); - - // it's ok to not wait for it here - syncService.sync(); - - return { result: 'success' }; - } - catch (e) { - log.error("Sync failed: " + e.message); - - return { - result: 'failure', - error: e.message - }; - } + return await setupService.setupSyncFromSyncServer(serverAddress, username, password); } module.exports = { diff --git a/src/routes/api/sync.js b/src/routes/api/sync.js index b37d2f954..7cfc2b469 100644 --- a/src/routes/api/sync.js +++ b/src/routes/api/sync.js @@ -24,7 +24,10 @@ async function testSync() { } async function getStats() { - return syncService.stats; + return { + initialized: await optionService.getOption('initialized') === 'true', + stats: syncService.stats + }; } async function checkSync() { diff --git a/src/routes/setup.js b/src/routes/setup.js index 79082b920..81a30ec38 100644 --- a/src/routes/setup.js +++ b/src/routes/setup.js @@ -1,7 +1,25 @@ "use strict"; -function setupPage(req, res) { - res.render('setup', {}); +const sqlInit = require('../services/sql_init'); +const setupService = require('../services/setup'); + +async function setupPage(req, res) { + if (await sqlInit.isDbInitialized()) { + res.redirect('/'); + } + + // we got here because DB is not completely initialized so if schema exists + // it means we're in sync in progress state. + const syncInProgress = await sqlInit.schemaExists(); + + if (syncInProgress) { + // trigger sync if it's not already running + setupService.triggerSync(); + } + + res.render('setup', { + syncInProgress: syncInProgress + }); } module.exports = { diff --git a/src/services/app_info.js b/src/services/app_info.js index 879f53c78..65dd9c0b5 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -3,7 +3,7 @@ const build = require('./build'); const packageJson = require('../../package'); -const APP_DB_VERSION = 102; +const APP_DB_VERSION = 103; const SYNC_VERSION = 1; module.exports = { diff --git a/src/services/options_init.js b/src/services/options_init.js index 8029c495e..b3b7aac90 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -29,7 +29,7 @@ async function initSyncedOptions(username, password) { await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16)); } -async function initNotSyncedOptions(startNotePath = '', syncServerHost = '') { +async function initNotSyncedOptions(initialized, startNotePath = '', syncServerHost = '') { await optionService.createOption('startNotePath', startNotePath, false); await optionService.createOption('lastBackupDate', dateUtils.nowDate(), false); await optionService.createOption('dbVersion', appInfo.dbVersion, false); @@ -43,6 +43,8 @@ async function initNotSyncedOptions(startNotePath = '', syncServerHost = '') { await optionService.createOption('syncServerHost', syncServerHost, false); await optionService.createOption('syncServerTimeout', 5000, false); await optionService.createOption('syncProxy', '', false); + + await optionService.createOption('initialized', initialized ? 'true' : 'false', false); } module.exports = { diff --git a/src/services/setup.js b/src/services/setup.js new file mode 100644 index 000000000..7e6a3a03b --- /dev/null +++ b/src/services/setup.js @@ -0,0 +1,66 @@ +const sqlInit = require('./sql_init'); +const sql = require('./sql'); +const rp = require('request-promise'); +const Option = require('../entities/option'); +const syncService = require('./sync'); +const log = require('./log'); +const optionService = require('./options'); + +function triggerSync() { +// it's ok to not wait for it here + syncService.sync().then(async () => { + await optionService.setOption('initialized', 'true'); + }); +} + +async function setupSyncFromSyncServer(serverAddress, username, password) { + if (await sqlInit.isDbInitialized()) { + return { + result: 'failure', + error: 'DB is already initialized.' + }; + } + + try { + log.info("Getting document options from sync server."); + + // response is expected to contain documentId and documentSecret options + const options = await rp.get({ + uri: serverAddress + '/api/sync/document', + auth: { + 'user': username, + 'pass': password + }, + json: true + }); + + log.info("Creating database for sync"); + + await sql.transactional(async () => { + await sqlInit.createDatabaseForSync(serverAddress); + + for (const opt of options) { + await new Option(opt).save(); + } + }); + + log.info("Triggering sync."); + + triggerSync(); + + return { result: 'success' }; + } + catch (e) { + log.error("Sync failed: " + e.message); + + return { + result: 'failure', + error: e.message + }; + } +} + +module.exports = { + setupSyncFromSyncServer, + triggerSync +}; \ No newline at end of file diff --git a/src/services/sql_init.js b/src/services/sql_init.js index e013f5be7..b6e9508ba 100644 --- a/src/services/sql_init.js +++ b/src/services/sql_init.js @@ -22,12 +22,22 @@ const dbReady = new Promise(async (resolve, reject) => { initDbConnection(); }); -async function isDbInitialized() { - const tableResults = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'"); +async function schemaExists() { + const tableResults = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='options'"); return tableResults.length === 1; } +async function isDbInitialized() { + if (!await schemaExists()) { + return false; + } + + const initialized = await sql.getValue("SELECT value FROM options WHERE name = 'initialized'"); + + return initialized === 'true'; +} + async function initDbConnection() { await cls.init(async () => { await sql.execute("PRAGMA foreign_keys = ON"); @@ -53,6 +63,10 @@ async function initDbConnection() { async function createInitialDatabase(username, password) { log.info("Creating initial database ..."); + if (await isDbInitialized()) { + throw new Error("DB is already initialized"); + } + const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8'); const notesSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_notes.sql', 'UTF-8'); const notesTreeSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_branches.sql', 'UTF-8'); @@ -72,7 +86,7 @@ async function createInitialDatabase(username, password) { await optionsInitService.initDocumentOptions(); await optionsInitService.initSyncedOptions(username, password); - await optionsInitService.initNotSyncedOptions(startNoteId); + await optionsInitService.initNotSyncedOptions(true, startNoteId); await require('./sync_table').fillAllSyncRows(); }); @@ -90,7 +104,7 @@ async function createDatabaseForSync(syncServerHost) { await sql.transactional(async () => { await sql.executeScript(schema); - await require('./options_init').initNotSyncedOptions('', syncServerHost); + await require('./options_init').initNotSyncedOptions(false, '', syncServerHost); }); log.info("Schema and not synced options generated."); @@ -110,6 +124,7 @@ async function isDbUpToDate() { module.exports = { dbReady, + schemaExists, isDbInitialized, initDbConnection, isDbUpToDate, diff --git a/src/views/setup.ejs b/src/views/setup.ejs index 8590e63df..e6ab086b9 100644 --- a/src/views/setup.ejs +++ b/src/views/setup.ejs @@ -103,6 +103,7 @@ const glob = { sourceId: '' }; + const syncInProgress = <%= syncInProgress ? 'true' : 'false' %>;