From a3f57622ff062014441a6e349d8eba76e8b686e4 Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 3 Dec 2017 19:18:33 -0500 Subject: [PATCH 01/15] distinguishing between when DB is just connected and when it's ready for queries (validated) --- bin/www | 3 ++- services/backup.js | 8 +++++--- services/ping_job.js | 6 ++++-- services/sql.js | 40 +++++++++++++++++++--------------------- services/sync.js | 42 ++++++++++++++++++++++-------------------- 5 files changed, 52 insertions(+), 47 deletions(-) diff --git a/bin/www b/bin/www index ca32874bb..5110e3f5b 100755 --- a/bin/www +++ b/bin/www @@ -18,6 +18,7 @@ const log = require('../services/log'); const app_info = require('../services/app_info'); const messaging = require('../services/messaging'); const utils = require('../services/utils'); +const sql = require('../services/sql'); const port = normalizePort(config['Network']['port'] || '3000'); app.set('port', port); @@ -53,7 +54,7 @@ httpServer.listen(port); httpServer.on('error', onError); httpServer.on('listening', onListening); -messaging.init(httpServer, sessionParser); +sql.dbReady.then(() => messaging.init(httpServer, sessionParser)); if (utils.isElectron()) { const electronRouting = require('../routes/electron'); diff --git a/services/backup.js b/services/backup.js index 513ba932e..9e355fc9e 100644 --- a/services/backup.js +++ b/services/backup.js @@ -58,10 +58,12 @@ if (!fs.existsSync(dataDir.BACKUP_DIR)) { fs.mkdirSync(dataDir.BACKUP_DIR, 0o700); } -setInterval(regularBackup, 60 * 60 * 1000); +sql.dbReady.then(() => { + setInterval(regularBackup, 60 * 60 * 1000); -// kickoff backup immediately -setTimeout(regularBackup, 1000); + // kickoff backup immediately + setTimeout(regularBackup, 1000); +}); module.exports = { backupNow diff --git a/services/ping_job.js b/services/ping_job.js index 80fc036b7..24445d0d1 100644 --- a/services/ping_job.js +++ b/services/ping_job.js @@ -8,7 +8,7 @@ const sync = require('./sync'); let startTime = utils.nowTimestamp(); let sentSyncId = []; -setInterval(async () => { +async function sendPing() { const syncs = await sql.getResults("SELECT * FROM sync WHERE sync_date >= ? AND source_id != ?", [startTime, source_id.currentSourceId]); startTime = utils.nowTimestamp(); @@ -41,4 +41,6 @@ setInterval(async () => { for (const syncId of syncIds) { sentSyncId.push(syncId); } -}, 1000); \ No newline at end of file +} + +sql.dbReady.then(() => setInterval(sendPing, 1000)); \ No newline at end of file diff --git a/services/sql.js b/services/sql.js index 49d867999..d98261282 100644 --- a/services/sql.js +++ b/services/sql.js @@ -2,13 +2,30 @@ const log = require('./log'); const dataDir = require('./data_dir'); +const fs = require('fs'); const sqlite = require('sqlite'); async function createConnection() { return await sqlite.open(dataDir.DOCUMENT_PATH, {Promise}); } -const dbReady = createConnection(); +const dbConnected = createConnection(); + +const dbReady = new Promise((resolve, reject) => { + dbConnected.then(async db => { + const tableResults = await getResults("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'"); + if (tableResults.length !== 1) { + console.log("No connection to initialized DB."); + process.exit(1); + } + + resolve(db); + }) + .catch(e => { + console.log("Error connecting to DB.", e); + process.exit(1); + }); +}); async function insert(table_name, rec, replace = false) { const keys = Object.keys(rec); @@ -44,13 +61,10 @@ async function rollback() { } async function getSingleResult(query, params = []) { - const db = await dbReady; - return await wrap(async db => db.get(query, ...params)); } async function getSingleResultOrNull(query, params = []) { - const db = await dbReady; const all = await wrap(async db => db.all(query, ...params)); return all.length > 0 ? all[0] : null; @@ -67,8 +81,6 @@ async function getSingleValue(query, params = []) { } async function getResults(query, params = []) { - const db = await dbReady; - return await wrap(async db => db.all(query, ...params)); } @@ -106,7 +118,7 @@ async function executeScript(query) { async function wrap(func) { const thisError = new Error(); - const db = await dbReady; + const db = await dbConnected; try { return await func(db); @@ -157,20 +169,6 @@ async function doInTransaction(func) { } } -dbReady - .then(async () => { - const tableResults = await getResults("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'"); - - if (tableResults.length !== 1) { - console.log("No connection to initialized DB."); - process.exit(1); - } - }) - .catch(e => { - console.log("Error connecting to DB.", e); - process.exit(1); - }); - module.exports = { dbReady, insert, diff --git a/services/sync.js b/services/sync.js index d59a74986..631c9a235 100644 --- a/services/sync.js +++ b/services/sync.js @@ -305,29 +305,31 @@ async function syncRequest(syncContext, method, uri, body) { } } -if (isSyncSetup) { - log.info("Setting up sync to " + SYNC_SERVER + " with timeout " + SYNC_TIMEOUT); +sql.dbReady.then(() => { + if (isSyncSetup) { + log.info("Setting up sync to " + SYNC_SERVER + " with timeout " + SYNC_TIMEOUT); - if (SYNC_PROXY) { - log.info("Sync proxy: " + SYNC_PROXY); + if (SYNC_PROXY) { + log.info("Sync proxy: " + SYNC_PROXY); + } + + const syncCertPath = config['Sync']['syncServerCertificate']; + + if (syncCertPath) { + log.info('Sync certificate: ' + syncCertPath); + + syncServerCertificate = fs.readFileSync(syncCertPath); + } + + setInterval(sync, 60000); + + // kickoff initial sync immediately + setTimeout(sync, 1000); } - - const syncCertPath = config['Sync']['syncServerCertificate']; - - if (syncCertPath) { - log.info('Sync certificate: ' + syncCertPath); - - syncServerCertificate = fs.readFileSync(syncCertPath); + else { + log.info("Sync server not configured, sync timer not running.") } - - setInterval(sync, 60000); - - // kickoff initial sync immediately - setTimeout(sync, 1000); -} -else { - log.info("Sync server not configured, sync timer not running.") -} +}); module.exports = { sync, From 65465488485327d8e0a6d317287d43510aacaf89 Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 3 Dec 2017 22:29:23 -0500 Subject: [PATCH 02/15] implemented initial setup of the app --- export-schema.sh | 3 ++ public/javascripts/setup.js | 34 ++++++++++++++ routes/api/login.js | 5 +- routes/api/migration.js | 3 +- routes/api/setup.js | 32 +++++++++++++ routes/routes.js | 4 ++ routes/setup.js | 11 +++++ schema.sql | 92 +++++++++++++++++++++++++++++++++++++ services/app_info.js | 5 +- services/auth.js | 28 +++++++++-- services/migration.js | 7 ++- services/options.js | 38 +++++++++++---- services/sql.js | 33 +++++++++++-- services/sync.js | 3 +- views/setup.ejs | 48 +++++++++++++++++++ 15 files changed, 316 insertions(+), 30 deletions(-) create mode 100755 export-schema.sh create mode 100644 public/javascripts/setup.js create mode 100644 routes/api/setup.js create mode 100644 routes/setup.js create mode 100644 schema.sql create mode 100644 views/setup.ejs diff --git a/export-schema.sh b/export-schema.sh new file mode 100755 index 000000000..95cc7655d --- /dev/null +++ b/export-schema.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +sqlite3 ~/trilium-data/document.db .schema > schema.sql \ No newline at end of file diff --git a/public/javascripts/setup.js b/public/javascripts/setup.js new file mode 100644 index 000000000..ddc9fa9c4 --- /dev/null +++ b/public/javascripts/setup.js @@ -0,0 +1,34 @@ +$("#setup-form").submit(() => { + const username = $("#username").val(); + const password1 = $("#password1").val(); + const password2 = $("#password2").val(); + + if (!username) { + showAlert("Username can't be empty"); + return false; + } + + if (!password1) { + showAlert("Password can't be empty"); + return false; + } + + if (password1 !== password2) { + showAlert("Both password fields need be identical."); + return false; + } + + server.post('setup', { + username: username, + password: password1 + }).then(() => { + window.location.replace("/"); + }); + + return false; +}); + +function showAlert(message) { + $("#alert").html(message); + $("#alert").show(); +} \ No newline at end of file diff --git a/routes/api/login.js b/routes/api/login.js index ea729254c..e09063e57 100644 --- a/routes/api/login.js +++ b/routes/api/login.js @@ -9,6 +9,7 @@ const source_id = require('../../services/source_id'); const auth = require('../../services/auth'); const password_encryption = require('../../services/password_encryption'); const protected_session = require('../../services/protected_session'); +const app_info = require('../../services/app_info'); router.post('/sync', async (req, res, next) => { const timestamp = req.body.timestamp; @@ -22,9 +23,9 @@ router.post('/sync', async (req, res, next) => { const dbVersion = req.body.dbVersion; - if (dbVersion !== migration.APP_DB_VERSION) { + if (dbVersion !== app_info.db_version) { res.status(400); - res.send({ message: 'Non-matching db versions, local is version ' + migration.APP_DB_VERSION }); + res.send({ message: 'Non-matching db versions, local is version ' + app_info.db_version }); } const documentSecret = await options.getOption('document_secret'); diff --git a/routes/api/migration.js b/routes/api/migration.js index cae0c1c4d..9d75ceab2 100644 --- a/routes/api/migration.js +++ b/routes/api/migration.js @@ -5,11 +5,12 @@ const router = express.Router(); const auth = require('../../services/auth'); const options = require('../../services/options'); const migration = require('../../services/migration'); +const app_info = require('../../services/app_info'); router.get('', auth.checkApiAuthForMigrationPage, async (req, res, next) => { res.send({ db_version: parseInt(await options.getOption('db_version')), - app_db_version: migration.APP_DB_VERSION + app_db_version: app_info.db_version }); }); diff --git a/routes/api/setup.js b/routes/api/setup.js new file mode 100644 index 000000000..ea31c84a7 --- /dev/null +++ b/routes/api/setup.js @@ -0,0 +1,32 @@ +"use strict"; + +const express = require('express'); +const router = express.Router(); +const auth = require('../../services/auth'); +const options = require('../../services/options'); +const sql = require('../../services/sql'); +const utils = require('../../services/utils'); +const my_scrypt = require('../../services/my_scrypt'); +const password_encryption = require('../../services/password_encryption'); + +router.post('', auth.checkAppNotInitialized, async (req, res, next) => { + const { username, password } = req.body; + + await sql.doInTransaction(async () => { + await options.setOption('username', username); + + await options.setOption('password_verification_salt', utils.randomSecureToken(32)); + await options.setOption('password_derived_key_salt', utils.randomSecureToken(32)); + + const passwordVerificationKey = utils.toBase64(await my_scrypt.getVerificationHash(password)); + await options.setOption('password_verification_hash', passwordVerificationKey); + + await password_encryption.setDataKey(password, utils.randomSecureToken(16)); + }); + + sql.setDbReadyAsResolved(); + + res.send({}); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/routes.js b/routes/routes.js index 37aa816b4..888dbfc0a 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -2,6 +2,7 @@ const indexRoute = require('./index'); const loginRoute = require('./login'); const logoutRoute = require('./logout'); const migrationRoute = require('./migration'); +const setupRoute = require('./setup'); // API routes const treeApiRoute = require('./api/tree'); @@ -19,12 +20,14 @@ const recentNotesRoute = require('./api/recent_notes'); const appInfoRoute = require('./api/app_info'); const exportRoute = require('./api/export'); const importRoute = require('./api/import'); +const setupApiRoute = require('./api/setup'); function register(app) { app.use('/', indexRoute); app.use('/login', loginRoute); app.use('/logout', logoutRoute); app.use('/migration', migrationRoute); + app.use('/setup', setupRoute); app.use('/api/tree', treeApiRoute); app.use('/api/notes', notesApiRoute); @@ -41,6 +44,7 @@ function register(app) { app.use('/api/app-info', appInfoRoute); app.use('/api/export', exportRoute); app.use('/api/import', importRoute); + app.use('/api/setup', setupApiRoute); } module.exports = { diff --git a/routes/setup.js b/routes/setup.js new file mode 100644 index 000000000..083c90611 --- /dev/null +++ b/routes/setup.js @@ -0,0 +1,11 @@ +"use strict"; + +const express = require('express'); +const router = express.Router(); +const auth = require('../services/auth'); + +router.get('', auth.checkAppNotInitialized, (req, res, next) => { + res.render('setup', {}); +}); + +module.exports = router; diff --git a/schema.sql b/schema.sql new file mode 100644 index 000000000..3dbe8b647 --- /dev/null +++ b/schema.sql @@ -0,0 +1,92 @@ +CREATE TABLE `migrations` ( + `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `name` TEXT NOT NULL, + `version` INTEGER NOT NULL, + `success` INTEGER NOT NULL, + `error` TEXT +); +CREATE TABLE `sync` ( + `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `entity_name` TEXT NOT NULL, + `entity_id` TEXT NOT NULL, + `sync_date` INTEGER NOT NULL +, source_id TEXT); +CREATE UNIQUE INDEX `IDX_sync_entity_name_id` ON `sync` ( + `entity_name`, + `entity_id` +); +CREATE INDEX `IDX_sync_sync_date` ON `sync` ( + `sync_date` +); +CREATE TABLE IF NOT EXISTS "options" ( + `opt_name` TEXT NOT NULL PRIMARY KEY, + `opt_value` TEXT, + `date_modified` INT +); +CREATE TABLE `event_log` ( + `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `note_id` TEXT, + `comment` TEXT, + `date_added` INTEGER NOT NULL +); +CREATE INDEX `IDX_event_log_date_added` ON `event_log` ( + `date_added` +); +CREATE TABLE IF NOT EXISTS "notes" ( + `note_id` TEXT NOT NULL, + `note_title` TEXT, + `note_text` TEXT, + `date_created` INT, + `date_modified` INT, + `is_protected` INT NOT NULL DEFAULT 0, + `is_deleted` INT NOT NULL DEFAULT 0, + PRIMARY KEY(`note_id`) +); +CREATE INDEX `IDX_notes_is_deleted` ON `notes` ( + `is_deleted` +); +CREATE TABLE IF NOT EXISTS "notes_history" ( + `note_history_id` TEXT NOT NULL PRIMARY KEY, + `note_id` TEXT NOT NULL, + `note_title` TEXT, + `note_text` TEXT, + `is_protected` INT, + `date_modified_from` INT, + `date_modified_to` INT +); +CREATE INDEX `IDX_notes_history_note_id` ON `notes_history` ( + `note_id` +); +CREATE INDEX `IDX_notes_history_note_date_modified_from` ON `notes_history` ( + `date_modified_from` +); +CREATE INDEX `IDX_notes_history_note_date_modified_to` ON `notes_history` ( + `date_modified_to` +); +CREATE TABLE `source_ids` ( + `source_id` TEXT NOT NULL, + `date_created` INTEGER NOT NULL, + PRIMARY KEY(`source_id`) +); +CREATE TABLE IF NOT EXISTS "notes_tree" ( + [note_tree_id] VARCHAR(30) PRIMARY KEY NOT NULL, + [note_id] VARCHAR(30) NOT NULL, + [note_pid] VARCHAR(30) NOT NULL, + [note_pos] INTEGER NOT NULL, + [is_expanded] BOOLEAN NULL , + date_modified INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0 +, `prefix` TEXT); +CREATE INDEX `IDX_notes_tree_note_tree_id` ON `notes_tree` ( + `note_tree_id` +); +CREATE INDEX `IDX_notes_tree_note_id_note_pid` ON `notes_tree` ( + `note_id`, + `note_pid` +); +CREATE TABLE `recent_notes` ( + 'note_tree_id'TEXT NOT NULL PRIMARY KEY, + `note_path` TEXT NOT NULL, + `date_accessed` INTEGER NOT NULL , + is_deleted INT +); diff --git a/services/app_info.js b/services/app_info.js index 74bab1ec0..4dba8a793 100644 --- a/services/app_info.js +++ b/services/app_info.js @@ -2,11 +2,12 @@ const build = require('./build'); const packageJson = require('../package'); -const migration = require('./migration'); + +const APP_DB_VERSION = 48; module.exports = { app_version: packageJson.version, - db_version: migration.APP_DB_VERSION, + db_version: APP_DB_VERSION, build_date: build.build_date, build_revision: build.build_revision }; \ No newline at end of file diff --git a/services/auth.js b/services/auth.js index 9ccb5da35..ca5d0a9c7 100644 --- a/services/auth.js +++ b/services/auth.js @@ -2,16 +2,22 @@ const migration = require('./migration'); const utils = require('./utils'); +const options = require('./options'); async function checkAuth(req, res, next) { - if (!req.session.loggedIn && !utils.isElectron()) { + const username = await options.getOption('username'); + + if (!username) { + res.redirect("setup"); + } + else if (!req.session.loggedIn && !utils.isElectron()) { res.redirect("login"); } - else if (await migration.isDbUpToDate()) { - next(); + else if (!await migration.isDbUpToDate()) { + res.redirect("migration"); } else { - res.redirect("migration"); + next(); } } @@ -45,9 +51,21 @@ async function checkApiAuthForMigrationPage(req, res, next) { } } +async function checkAppNotInitialized(req, res, next) { + const username = await options.getOption('username'); + + if (username) { + res.status(400).send("App already initialized."); + } + else { + next(); + } +} + module.exports = { checkAuth, checkAuthForMigrationPage, checkApiAuth, - checkApiAuthForMigrationPage + checkApiAuthForMigrationPage, + checkAppNotInitialized }; \ No newline at end of file diff --git a/services/migration.js b/services/migration.js index 39771eac4..ce6e88e56 100644 --- a/services/migration.js +++ b/services/migration.js @@ -3,8 +3,8 @@ const sql = require('./sql'); const options = require('./options'); const fs = require('fs-extra'); const log = require('./log'); +const app_info = require('./app_info'); -const APP_DB_VERSION = 48; const MIGRATIONS_DIR = "migrations"; async function migrate() { @@ -84,11 +84,10 @@ async function migrate() { async function isDbUpToDate() { const dbVersion = parseInt(await options.getOption('db_version')); - return dbVersion >= APP_DB_VERSION; + return dbVersion >= app_info.db_version; } module.exports = { migrate, - isDbUpToDate, - APP_DB_VERSION + isDbUpToDate }; \ No newline at end of file diff --git a/services/options.js b/services/options.js index c6e236e21..839838409 100644 --- a/services/options.js +++ b/services/options.js @@ -1,6 +1,7 @@ const sql = require('./sql'); const utils = require('./utils'); const sync_table = require('./sync_table'); +const app_info = require('./app_info'); const SYNCED_OPTIONS = [ 'username', 'password_verification_hash', 'encrypted_data_key', 'protected_session_timeout', 'history_snapshot_time_interval' ]; @@ -20,21 +21,38 @@ async function setOption(optName, optValue) { await sync_table.addOptionsSync(optName); } - await sql.execute("UPDATE options SET opt_value = ?, date_modified = ? WHERE opt_name = ?", - [optValue, utils.nowTimestamp(), optName]); + await sql.replace("options", { + opt_name: optName, + opt_value: optValue, + date_modified: utils.nowTimestamp() + }); } -sql.dbReady.then(async () => { - if (!await getOption('document_id') || !await getOption('document_secret')) { - await sql.doInTransaction(async () => { - await setOption('document_id', utils.randomSecureToken(16)); - await setOption('document_secret', utils.randomSecureToken(16)); - }); - } -}); +async function initOptions() { + await setOption('document_id', utils.randomSecureToken(16)); + await setOption('document_secret', utils.randomSecureToken(16)); + + await setOption('username', ''); + await setOption('password_verification_hash', ''); + await setOption('password_verification_salt', ''); + await setOption('password_derived_key_salt', ''); + await setOption('encrypted_data_key', ''); + await setOption('encrypted_data_key_iv', ''); + + await setOption('start_note_tree_id', ''); + await setOption('protected_session_timeout', 600); + await setOption('history_snapshot_time_interval', 600); + await setOption('last_backup_date', utils.nowTimestamp()); + await setOption('db_version', app_info.db_version); + + await setOption('last_synced_pull', app_info.db_version); + await setOption('last_synced_push', 0); + await setOption('last_synced_push', 0); +} module.exports = { getOption, setOption, + initOptions, SYNCED_OPTIONS }; \ No newline at end of file diff --git a/services/sql.js b/services/sql.js index d98261282..611760ffe 100644 --- a/services/sql.js +++ b/services/sql.js @@ -11,15 +11,33 @@ async function createConnection() { const dbConnected = createConnection(); +let dbReadyResolve = null; const dbReady = new Promise((resolve, reject) => { dbConnected.then(async db => { + dbReadyResolve = () => resolve(db); + const tableResults = await getResults("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'"); if (tableResults.length !== 1) { - console.log("No connection to initialized DB."); - process.exit(1); - } + log.info("Connected to db, but schema doesn't exist. Initializing schema ..."); - resolve(db); + const schema = fs.readFileSync('schema.sql', 'UTF-8'); + + await doInTransaction(async () => { + await executeScript(schema); + + await require('./options').initOptions(); + }); + + // we don't resolve dbReady promise because user needs to setup the username and password to initialize + // the database + } + else { + const username = await getSingleValue("SELECT opt_value FROM options WHERE opt_name = 'username'"); + + if (username) { + resolve(db); + } + } }) .catch(e => { console.log("Error connecting to DB.", e); @@ -27,6 +45,10 @@ const dbReady = new Promise((resolve, reject) => { }); }); +function setDbReadyAsResolved() { + dbReadyResolve(); +} + async function insert(table_name, rec, replace = false) { const keys = Object.keys(rec); if (keys.length === 0) { @@ -181,5 +203,6 @@ module.exports = { getFlattenedResults, execute, executeScript, - doInTransaction + doInTransaction, + setDbReadyAsResolved }; \ No newline at end of file diff --git a/services/sync.js b/services/sync.js index 631c9a235..91d350eb3 100644 --- a/services/sync.js +++ b/services/sync.js @@ -13,6 +13,7 @@ const syncUpdate = require('./sync_update'); const content_hash = require('./content_hash'); const event_log = require('./event_log'); const fs = require('fs'); +const app_info = require('./app_info'); const SYNC_SERVER = config['Sync']['syncServerHost']; const isSyncSetup = !!SYNC_SERVER; @@ -94,7 +95,7 @@ async function login() { const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', { timestamp: timestamp, - dbVersion: migration.APP_DB_VERSION, + dbVersion: app_info.db_version, hash: hash }); diff --git a/views/setup.ejs b/views/setup.ejs new file mode 100644 index 000000000..8974ef94e --- /dev/null +++ b/views/setup.ejs @@ -0,0 +1,48 @@ + + + + + Setup + + +
+

Trilium setup

+ + + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+ + + + + + + + + + + + + + + + \ No newline at end of file From 3789ead2daacdfc8779e07b7208562345db05c32 Mon Sep 17 00:00:00 2001 From: azivner Date: Mon, 4 Dec 2017 00:17:08 -0500 Subject: [PATCH 03/15] creating new starting page when creating database --- services/options.js | 4 ++-- services/sql.js | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/services/options.js b/services/options.js index 839838409..bac7f6da8 100644 --- a/services/options.js +++ b/services/options.js @@ -28,7 +28,7 @@ async function setOption(optName, optValue) { }); } -async function initOptions() { +async function initOptions(startNoteTreeId) { await setOption('document_id', utils.randomSecureToken(16)); await setOption('document_secret', utils.randomSecureToken(16)); @@ -39,7 +39,7 @@ async function initOptions() { await setOption('encrypted_data_key', ''); await setOption('encrypted_data_key_iv', ''); - await setOption('start_note_tree_id', ''); + await setOption('start_note_tree_id', startNoteTreeId); await setOption('protected_session_timeout', 600); await setOption('history_snapshot_time_interval', 600); await setOption('last_backup_date', utils.nowTimestamp()); diff --git a/services/sql.js b/services/sql.js index 611760ffe..8c232a3b7 100644 --- a/services/sql.js +++ b/services/sql.js @@ -4,6 +4,7 @@ const log = require('./log'); const dataDir = require('./data_dir'); const fs = require('fs'); const sqlite = require('sqlite'); +const utils = require('./utils'); async function createConnection() { return await sqlite.open(dataDir.DOCUMENT_PATH, {Promise}); @@ -25,7 +26,28 @@ const dbReady = new Promise((resolve, reject) => { await doInTransaction(async () => { await executeScript(schema); - await require('./options').initOptions(); + const noteId = utils.newNoteId(); + + await insert('notes_tree', { + note_tree_id: utils.newNoteTreeId(), + note_id: noteId, + note_pid: 'root', + note_pos: 1, + is_deleted: 0, + date_modified: utils.nowTimestamp() + }); + + await insert('notes', { + note_id: noteId, + note_title: 'Welcome to Trilium!', + note_text: 'Text', + is_protected: 0, + is_deleted: 0, + date_created: utils.nowTimestamp(), + date_modified: utils.nowTimestamp() + }); + + await require('./options').initOptions(noteId); }); // we don't resolve dbReady promise because user needs to setup the username and password to initialize From 33ab70920949d0093e93fe2370eb31c3fecfdbe9 Mon Sep 17 00:00:00 2001 From: azivner Date: Mon, 4 Dec 2017 18:21:57 -0500 Subject: [PATCH 04/15] spacing between action buttons and pointer cursor --- public/stylesheets/style.css | 4 ++++ views/index.ejs | 28 +++++++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 61c90e2c0..bfe20189f 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -83,6 +83,10 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit text-decoration: none; } +.icon-action { + cursor: pointer; +} + #protect-button, #unprotect-button { display: none; } diff --git a/views/index.ejs b/views/index.ejs index 1e92f0d62..ced6e97b4 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -33,22 +33,24 @@ -
- - Create new top level note - +
+
-
+
Create new top level note From a7831ebfcd09eaa1e363078e967046853acf2506 Mon Sep 17 00:00:00 2001 From: azivner Date: Wed, 6 Dec 2017 19:42:23 -0500 Subject: [PATCH 06/15] log data dir location --- services/build.js | 2 +- services/log.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/services/build.js b/services/build.js index 129528bca..49fc86a7f 100644 --- a/services/build.js +++ b/services/build.js @@ -1 +1 @@ -module.exports = { build_date:"2017-11-30T00:11:04-05:00", build_revision: "719f5530544efa1d7aae16afd8a9e64db04ff206" }; +module.exports = { build_date:"2017-12-04T23:02:48-05:00", build_revision: "23e8e20d44ef029e6891dec6d2d855d282996823" }; diff --git a/services/log.js b/services/log.js index 6cd78cded..f00b2d037 100644 --- a/services/log.js +++ b/services/log.js @@ -1,15 +1,15 @@ "use strict"; const fs = require('fs'); -const LOG_DIR = require('./data_dir').LOG_DIR; +const data_dir = require('./data_dir'); -if (!fs.existsSync(LOG_DIR)) { - fs.mkdirSync(LOG_DIR, 0o700); +if (!fs.existsSync(data_dir.LOG_DIR)) { + fs.mkdirSync(data_dir.LOG_DIR, 0o700); } const logger = require('simple-node-logger').createRollingFileLogger({ errorEventName: 'error', - logDirectory: LOG_DIR, + logDirectory: data_dir.LOG_DIR, fileNamePattern: 'trilium-.log', dateFormat:'YYYY-MM-DD' }); @@ -37,6 +37,8 @@ function request(req) { logger.info(req.method + " " + req.url); } +info("Using data dir: " + data_dir.TRILIUM_DATA_DIR); + module.exports = { info, error, From a0bbd8c8531b715517a47852dd3f24a559e495ef Mon Sep 17 00:00:00 2001 From: azivner Date: Wed, 6 Dec 2017 19:53:23 -0500 Subject: [PATCH 07/15] throwException instead of throwing exceptions manually (includes stacktrace) --- public/javascripts/context_menu.js | 4 ++-- public/javascripts/note_tree.js | 4 ++-- public/javascripts/utils.js | 4 ++++ services/data_encryption.js | 2 +- services/migration.js | 2 +- services/notes.js | 2 +- services/options.js | 2 +- services/request_context.js | 2 +- services/sync.js | 6 +++--- 9 files changed, 16 insertions(+), 12 deletions(-) diff --git a/public/javascripts/context_menu.js b/public/javascripts/context_menu.js index 9f5043363..59e498479 100644 --- a/public/javascripts/context_menu.js +++ b/public/javascripts/context_menu.js @@ -19,7 +19,7 @@ const contextMenu = (function() { // just do nothing } else { - throw new Error("Unrecognized clipboard mode=" + clipboardMode); + throwError("Unrecognized clipboard mode=" + clipboardMode); } clipboardId = null; @@ -36,7 +36,7 @@ const contextMenu = (function() { treeChanges.cloneNoteTo(clipboardId, node.data.note_id); } else { - throw new Error("Unrecognized clipboard mode=" + mode); + throwError("Unrecognized clipboard mode=" + mode); } clipboardId = null; diff --git a/public/javascripts/note_tree.js b/public/javascripts/note_tree.js index 8fa2d537d..5f21c7cdb 100644 --- a/public/javascripts/note_tree.js +++ b/public/javascripts/note_tree.js @@ -25,7 +25,7 @@ const noteTree = (function() { let title = noteIdToTitle[noteId]; if (!title) { - throw new Error("Can't find title for noteId='" + noteId + "'"); + throwError("Can't find title for noteId='" + noteId + "'"); } if (parentNoteId !== null) { @@ -265,7 +265,7 @@ const noteTree = (function() { const parents = childToParents[noteId]; if (!parents) { - throw new Error("Can't find parents for noteId=" + noteId); + throwError("Can't find parents for noteId=" + noteId); } if (parents.length <= 1) { diff --git a/public/javascripts/utils.js b/public/javascripts/utils.js index 030ba1e8b..97d5f9843 100644 --- a/public/javascripts/utils.js +++ b/public/javascripts/utils.js @@ -30,6 +30,10 @@ function showError(message) { }); } +function throwError(message) { + throw new Error(message + ':' + new Error().stack); +} + function getDateFromTS(timestamp) { // Date accepts number of milliseconds since epoch so UTC timestamp works without any extra handling // see https://stackoverflow.com/questions/4631928/convert-utc-epoch-to-local-date-with-javascript diff --git a/services/data_encryption.js b/services/data_encryption.js index 31ebf9044..84b8174e9 100644 --- a/services/data_encryption.js +++ b/services/data_encryption.js @@ -31,7 +31,7 @@ function pad(data) { function encrypt(key, iv, plainText) { if (!key) { - throw new Error("No data key!"); + throwError("No data key!"); } const plainTextBuffer = Buffer.from(plainText); diff --git a/services/migration.js b/services/migration.js index ce6e88e56..5a02cb05c 100644 --- a/services/migration.js +++ b/services/migration.js @@ -58,7 +58,7 @@ async function migrate() { await migrationModule(db); } else { - throw new Error("Unknown migration type " + mig.type); + throwError("Unknown migration type " + mig.type); } await options.setOption("db_version", mig.dbVersion); diff --git a/services/notes.js b/services/notes.js index 95e8cc4fb..c57d0d05a 100644 --- a/services/notes.js +++ b/services/notes.js @@ -28,7 +28,7 @@ async function createNewNote(parentNoteId, note) { await sync_table.addNoteReorderingSync(parentNoteId); } else { - throw new Error('Unknown target: ' + note.target); + throwError('Unknown target: ' + note.target); } diff --git a/services/options.js b/services/options.js index bac7f6da8..a14b2d9ed 100644 --- a/services/options.js +++ b/services/options.js @@ -10,7 +10,7 @@ async function getOption(optName) { const row = await sql.getSingleResultOrNull("SELECT opt_value FROM options WHERE opt_name = ?", [optName]); if (!row) { - throw new Error("Option " + optName + " doesn't exist"); + throwError("Option " + optName + " doesn't exist"); } return row['opt_value']; diff --git a/services/request_context.js b/services/request_context.js index bb12d6ff7..ce43d926f 100644 --- a/services/request_context.js +++ b/services/request_context.js @@ -9,7 +9,7 @@ module.exports = function(req) { function getDataKey() { if (!isProtectedSessionAvailable()) { - throw new Error("Protected session is not available"); + throwError("Protected session is not available"); } return protected_session.getDataKey(req); diff --git a/services/sync.js b/services/sync.js index 91d350eb3..92d8964b3 100644 --- a/services/sync.js +++ b/services/sync.js @@ -157,7 +157,7 @@ async function pullSync(syncContext) { await syncUpdate.updateRecentNotes(resp, syncContext.sourceId); } else { - throw new Error("Unrecognized entity type " + sync.entity_name); + throwError("Unrecognized entity type " + sync.entity_name); } await setLastSyncedPull(sync.id); @@ -228,7 +228,7 @@ async function readAndPushEntity(sync, syncContext) { entity = await sql.getSingleResult('SELECT * FROM recent_notes WHERE note_tree_id = ?', [sync.entity_id]); } else { - throw new Error("Unrecognized entity type " + sync.entity_name); + throwError("Unrecognized entity type " + sync.entity_name); } if (!entity) { @@ -302,7 +302,7 @@ async function syncRequest(syncContext, method, uri, body) { return await rp(options); } catch (e) { - throw new Error("Request to " + method + " " + fullUri + " failed, inner exception: " + e.stack); + throwError("Request to " + method + " " + fullUri + " failed, inner exception: " + e.stack); } } From 0c6521545aed42073699e0f50ed380326e59d735 Mon Sep 17 00:00:00 2001 From: azivner Date: Wed, 6 Dec 2017 20:11:45 -0500 Subject: [PATCH 08/15] fix for cloned notes at root level + better logging --- public/javascripts/messaging.js | 2 +- public/javascripts/note_tree.js | 11 ++++++++++- public/javascripts/utils.js | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/public/javascripts/messaging.js b/public/javascripts/messaging.js index af3888e16..1a1b272be 100644 --- a/public/javascripts/messaging.js +++ b/public/javascripts/messaging.js @@ -4,7 +4,7 @@ const messaging = (function() { let ws = null; function logError(message) { - console.error(message); + console.trace(message); if (ws && ws.readyState === 1) { ws.send(JSON.stringify({ diff --git a/public/javascripts/note_tree.js b/public/javascripts/note_tree.js index 5f21c7cdb..68c020a49 100644 --- a/public/javascripts/note_tree.js +++ b/public/javascripts/note_tree.js @@ -278,7 +278,9 @@ const noteTree = (function() { const list = $("
    "); for (const parentNoteId of parents) { - const notePath = getSomeNotePath(parentNoteId) + '/' + noteId; + const parentNotePath = getSomeNotePath(parentNoteId); + // this is to avoid having root notes leading '/' + const notePath = parentNotePath ? (parentNotePath + '/' + noteId) : noteId; const title = getNotePathTitle(notePath); let item; @@ -302,12 +304,19 @@ const noteTree = (function() { let parentNoteId = 'root'; + console.log('notePath: ' + notePath); + console.log('notePath chunks: ', notePath.split('/')); + for (const noteId of notePath.split('/')) { + console.log('noteId: ' + noteId); + titlePath.push(getNoteTitle(noteId, parentNoteId)); parentNoteId = noteId; } + console.log("Title path:", titlePath.join(' / ')); + return titlePath.join(' / '); } diff --git a/public/javascripts/utils.js b/public/javascripts/utils.js index 97d5f9843..ab03172be 100644 --- a/public/javascripts/utils.js +++ b/public/javascripts/utils.js @@ -31,7 +31,9 @@ function showError(message) { } function throwError(message) { - throw new Error(message + ':' + new Error().stack); + messaging.logError(message); + + throw new Error(message); } function getDateFromTS(timestamp) { From d467fbdff3e38d061cd4e3d397fadcd0cafb4d30 Mon Sep 17 00:00:00 2001 From: azivner Date: Wed, 6 Dec 2017 20:19:41 -0500 Subject: [PATCH 09/15] better styling of parent list --- public/stylesheets/style.css | 11 +++++++++++ views/index.ejs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index bfe20189f..f8aef6366 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -117,6 +117,17 @@ div.ui-tooltip { width: auto; } +#parent-list { + display: none; + margin-left: 20px; + border-top: 2px solid #eee; + padding-top: 10px; +} + +#parent-list ul { + padding-left: 20px; +} + #loader-wrapper{position:fixed;top:0;left:0;width:100%;height:100%;z-index:1000;background-color:#fff;opacity:1;transition:opacity 2s ease} #loader{display:block;position:relative;left:50%;top:50%;width:150px;height:150px;margin:-75px 0 0 -75px;border-radius:50%;border:3px solid transparent;border-top-color:#777;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite} #loader:before{content:"";position:absolute;top:5px;left:5px;right:5px;bottom:5px;border-radius:50%;border:3px solid transparent;border-top-color:#aaa;-webkit-animation:spin 3s linear infinite;animation:spin 3s linear infinite} diff --git a/views/index.ejs b/views/index.ejs index 064cc39b3..17140dbbe 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -65,7 +65,7 @@
    -