mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'master' into ckeditor
This commit is contained in:
commit
7ca043ebc6
3
bin/www
3
bin/www
@ -18,6 +18,7 @@ const log = require('../services/log');
|
|||||||
const app_info = require('../services/app_info');
|
const app_info = require('../services/app_info');
|
||||||
const messaging = require('../services/messaging');
|
const messaging = require('../services/messaging');
|
||||||
const utils = require('../services/utils');
|
const utils = require('../services/utils');
|
||||||
|
const sql = require('../services/sql');
|
||||||
|
|
||||||
const port = normalizePort(config['Network']['port'] || '3000');
|
const port = normalizePort(config['Network']['port'] || '3000');
|
||||||
app.set('port', port);
|
app.set('port', port);
|
||||||
@ -53,7 +54,7 @@ httpServer.listen(port);
|
|||||||
httpServer.on('error', onError);
|
httpServer.on('error', onError);
|
||||||
httpServer.on('listening', onListening);
|
httpServer.on('listening', onListening);
|
||||||
|
|
||||||
messaging.init(httpServer, sessionParser);
|
sql.dbReady.then(() => messaging.init(httpServer, sessionParser));
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
const electronRouting = require('../routes/electron');
|
const electronRouting = require('../routes/electron');
|
||||||
|
3
export-schema.sh
Executable file
3
export-schema.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
sqlite3 ~/trilium-data/document.db .schema > schema.sql
|
@ -0,0 +1 @@
|
|||||||
|
UPDATE options SET opt_name = 'start_note_path' WHERE opt_name = 'start_note_tree_id';
|
33
package-lock.json
generated
33
package-lock.json
generated
@ -2419,19 +2419,19 @@
|
|||||||
"integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo="
|
"integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo="
|
||||||
},
|
},
|
||||||
"electron": {
|
"electron": {
|
||||||
"version": "1.8.2-beta.2",
|
"version": "1.8.2-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-1.8.2-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-1.8.2-beta.3.tgz",
|
||||||
"integrity": "sha1-tTLHEFDd0tSwDi4NNV3k51vB5f0=",
|
"integrity": "sha1-Ljkcy9YnaKOzsmC48uPxZHNWeuw=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "8.0.49",
|
"@types/node": "8.0.56",
|
||||||
"electron-download": "3.3.0",
|
"electron-download": "3.3.0",
|
||||||
"extract-zip": "1.6.5"
|
"extract-zip": "1.6.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "8.0.49",
|
"version": "8.0.56",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.49.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.56.tgz",
|
||||||
"integrity": "sha512-Oq3cV/mrMKy6Tv42llfS8YIH30ooDdhbJ40h1zoWl+goOJw8Kjy8j8RfjGZtZIUDO0gLwCfcbYM7+LModnbeMw=="
|
"integrity": "sha512-JAlQv3hUWbrnruuTiLDf1scd4F/TBT0LgGEe+BBeF3p/Rc3yL6RV57WJN2nK5i+BshEz1sDllwH0Fzbuo7G4QA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2590,7 +2590,7 @@
|
|||||||
},
|
},
|
||||||
"fs-extra": {
|
"fs-extra": {
|
||||||
"version": "0.30.0",
|
"version": "0.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
|
"resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
|
||||||
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
|
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "4.1.11",
|
"graceful-fs": "4.1.11",
|
||||||
@ -2807,6 +2807,23 @@
|
|||||||
"yargs": "6.6.0"
|
"yargs": "6.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/node": {
|
||||||
|
"version": "8.0.56",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.56.tgz",
|
||||||
|
"integrity": "sha512-JAlQv3hUWbrnruuTiLDf1scd4F/TBT0LgGEe+BBeF3p/Rc3yL6RV57WJN2nK5i+BshEz1sDllwH0Fzbuo7G4QA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"electron": {
|
||||||
|
"version": "1.8.2-beta.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/electron/-/electron-1.8.2-beta.2.tgz",
|
||||||
|
"integrity": "sha1-tTLHEFDd0tSwDi4NNV3k51vB5f0=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "8.0.56",
|
||||||
|
"electron-download": "3.3.0",
|
||||||
|
"extract-zip": "1.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
"version": "6.6.0",
|
"version": "6.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz",
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"debug": "~3.1.0",
|
"debug": "~3.1.0",
|
||||||
"devtron": "^1.4.0",
|
"devtron": "^1.4.0",
|
||||||
"ejs": "~2.5.7",
|
"ejs": "~2.5.7",
|
||||||
"electron": "^1.8.2-beta.2",
|
"electron": "^1.8.2-beta.3",
|
||||||
"electron-debug": "^1.0.0",
|
"electron-debug": "^1.0.0",
|
||||||
"express": "~4.16.2",
|
"express": "~4.16.2",
|
||||||
"express-session": "^1.15.6",
|
"express-session": "^1.15.6",
|
||||||
|
@ -19,7 +19,7 @@ const contextMenu = (function() {
|
|||||||
// just do nothing
|
// just do nothing
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error("Unrecognized clipboard mode=" + clipboardMode);
|
throwError("Unrecognized clipboard mode=" + clipboardMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
clipboardId = null;
|
clipboardId = null;
|
||||||
@ -36,7 +36,7 @@ const contextMenu = (function() {
|
|||||||
treeChanges.cloneNoteTo(clipboardId, node.data.note_id);
|
treeChanges.cloneNoteTo(clipboardId, node.data.note_id);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error("Unrecognized clipboard mode=" + mode);
|
throwError("Unrecognized clipboard mode=" + mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
clipboardId = null;
|
clipboardId = null;
|
||||||
|
@ -27,7 +27,7 @@ const noteHistory = (function() {
|
|||||||
historyItems = await server.get('notes-history/' + noteId);
|
historyItems = await server.get('notes-history/' + noteId);
|
||||||
|
|
||||||
for (const item of historyItems) {
|
for (const item of historyItems) {
|
||||||
const dateModified = getDateFromTS(item.date_modified_to);
|
const dateModified = getDateFromTS(item.date_modified_from);
|
||||||
|
|
||||||
$("#note-history-list").append($('<option>', {
|
$("#note-history-list").append($('<option>', {
|
||||||
value: item.note_history_id,
|
value: item.note_history_id,
|
||||||
|
@ -4,7 +4,7 @@ const messaging = (function() {
|
|||||||
let ws = null;
|
let ws = null;
|
||||||
|
|
||||||
function logError(message) {
|
function logError(message) {
|
||||||
console.error(message);
|
console.trace(message);
|
||||||
|
|
||||||
if (ws && ws.readyState === 1) {
|
if (ws && ws.readyState === 1) {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
|
@ -4,7 +4,7 @@ const noteTree = (function() {
|
|||||||
const treeEl = $("#tree");
|
const treeEl = $("#tree");
|
||||||
const parentListEl = $("#parent-list");
|
const parentListEl = $("#parent-list");
|
||||||
|
|
||||||
let startNoteTreeId = null;
|
let startNotePath = null;
|
||||||
let notesTreeMap = {};
|
let notesTreeMap = {};
|
||||||
|
|
||||||
let parentToChildren = {};
|
let parentToChildren = {};
|
||||||
@ -25,7 +25,7 @@ const noteTree = (function() {
|
|||||||
let title = noteIdToTitle[noteId];
|
let title = noteIdToTitle[noteId];
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
throw new Error("Can't find title for noteId='" + noteId + "'");
|
throwError("Can't find title for noteId='" + noteId + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentNoteId !== null) {
|
if (parentNoteId !== null) {
|
||||||
@ -265,7 +265,7 @@ const noteTree = (function() {
|
|||||||
const parents = childToParents[noteId];
|
const parents = childToParents[noteId];
|
||||||
|
|
||||||
if (!parents) {
|
if (!parents) {
|
||||||
throw new Error("Can't find parents for noteId=" + noteId);
|
throwError("Can't find parents for noteId=" + noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parents.length <= 1) {
|
if (parents.length <= 1) {
|
||||||
@ -278,7 +278,9 @@ const noteTree = (function() {
|
|||||||
const list = $("<ul/>");
|
const list = $("<ul/>");
|
||||||
|
|
||||||
for (const parentNoteId of parents) {
|
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);
|
const title = getNotePathTitle(notePath);
|
||||||
|
|
||||||
let item;
|
let item;
|
||||||
@ -303,6 +305,8 @@ const noteTree = (function() {
|
|||||||
let parentNoteId = 'root';
|
let parentNoteId = 'root';
|
||||||
|
|
||||||
for (const noteId of notePath.split('/')) {
|
for (const noteId of notePath.split('/')) {
|
||||||
|
console.log('noteId: ' + noteId);
|
||||||
|
|
||||||
titlePath.push(getNoteTitle(noteId, parentNoteId));
|
titlePath.push(getNoteTitle(noteId, parentNoteId));
|
||||||
|
|
||||||
parentNoteId = noteId;
|
parentNoteId = noteId;
|
||||||
@ -403,8 +407,8 @@ const noteTree = (function() {
|
|||||||
setExpandedToServer(data.node.data.note_tree_id, false);
|
setExpandedToServer(data.node.data.note_tree_id, false);
|
||||||
},
|
},
|
||||||
init: (event, data) => {
|
init: (event, data) => {
|
||||||
if (startNoteTreeId) {
|
if (startNotePath) {
|
||||||
activateNode(startNoteTreeId);
|
activateNode(startNotePath);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showAppIfHidden();
|
showAppIfHidden();
|
||||||
@ -494,10 +498,10 @@ const noteTree = (function() {
|
|||||||
|
|
||||||
function loadTree() {
|
function loadTree() {
|
||||||
return server.get('tree').then(resp => {
|
return server.get('tree').then(resp => {
|
||||||
startNoteTreeId = resp.start_note_tree_id;
|
startNotePath = resp.start_note_path;
|
||||||
|
|
||||||
if (document.location.hash) {
|
if (document.location.hash) {
|
||||||
startNoteTreeId = document.location.hash.substr(1); // strip initial #
|
startNotePath = document.location.hash.substr(1); // strip initial #
|
||||||
}
|
}
|
||||||
|
|
||||||
return prepareNoteTree(resp.notes, resp.notes_parent);
|
return prepareNoteTree(resp.notes, resp.notes_parent);
|
||||||
|
34
public/javascripts/setup.js
Normal file
34
public/javascripts/setup.js
Normal file
@ -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();
|
||||||
|
}
|
@ -30,6 +30,12 @@ function showError(message) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function throwError(message) {
|
||||||
|
messaging.logError(message);
|
||||||
|
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
function getDateFromTS(timestamp) {
|
function getDateFromTS(timestamp) {
|
||||||
// Date accepts number of milliseconds since epoch so UTC timestamp works without any extra handling
|
// 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
|
// see https://stackoverflow.com/questions/4631928/convert-utc-epoch-to-local-date-with-javascript
|
||||||
|
@ -85,6 +85,10 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-action {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
#protect-button, #unprotect-button {
|
#protect-button, #unprotect-button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -115,6 +119,17 @@ div.ui-tooltip {
|
|||||||
width: auto;
|
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-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{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}
|
#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}
|
||||||
|
@ -9,6 +9,7 @@ const source_id = require('../../services/source_id');
|
|||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
const password_encryption = require('../../services/password_encryption');
|
const password_encryption = require('../../services/password_encryption');
|
||||||
const protected_session = require('../../services/protected_session');
|
const protected_session = require('../../services/protected_session');
|
||||||
|
const app_info = require('../../services/app_info');
|
||||||
|
|
||||||
router.post('/sync', async (req, res, next) => {
|
router.post('/sync', async (req, res, next) => {
|
||||||
const timestamp = req.body.timestamp;
|
const timestamp = req.body.timestamp;
|
||||||
@ -22,9 +23,9 @@ router.post('/sync', async (req, res, next) => {
|
|||||||
|
|
||||||
const dbVersion = req.body.dbVersion;
|
const dbVersion = req.body.dbVersion;
|
||||||
|
|
||||||
if (dbVersion !== migration.APP_DB_VERSION) {
|
if (dbVersion !== app_info.db_version) {
|
||||||
res.status(400);
|
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');
|
const documentSecret = await options.getOption('document_secret');
|
||||||
|
@ -5,11 +5,12 @@ const router = express.Router();
|
|||||||
const auth = require('../../services/auth');
|
const auth = require('../../services/auth');
|
||||||
const options = require('../../services/options');
|
const options = require('../../services/options');
|
||||||
const migration = require('../../services/migration');
|
const migration = require('../../services/migration');
|
||||||
|
const app_info = require('../../services/app_info');
|
||||||
|
|
||||||
router.get('', auth.checkApiAuthForMigrationPage, async (req, res, next) => {
|
router.get('', auth.checkApiAuthForMigrationPage, async (req, res, next) => {
|
||||||
res.send({
|
res.send({
|
||||||
db_version: parseInt(await options.getOption('db_version')),
|
db_version: parseInt(await options.getOption('db_version')),
|
||||||
app_db_version: migration.APP_DB_VERSION
|
app_db_version: app_info.db_version
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ router.put('/:noteTreeId/:notePath', auth.checkApiAuth, async (req, res, next) =
|
|||||||
|
|
||||||
await sync_table.addRecentNoteSync(noteTreeId);
|
await sync_table.addRecentNoteSync(noteTreeId);
|
||||||
|
|
||||||
await options.setOption('start_note_tree_id', notePath);
|
await options.setOption('start_note_path', notePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
res.send(await getRecentNotes());
|
res.send(await getRecentNotes());
|
||||||
|
32
routes/api/setup.js
Normal file
32
routes/api/setup.js
Normal file
@ -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;
|
@ -31,7 +31,7 @@ router.get('/', auth.checkApiAuth, async (req, res, next) => {
|
|||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
notes: notes,
|
notes: notes,
|
||||||
start_note_tree_id: await options.getOption('start_note_tree_id')
|
start_note_path: await options.getOption('start_note_path')
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ const indexRoute = require('./index');
|
|||||||
const loginRoute = require('./login');
|
const loginRoute = require('./login');
|
||||||
const logoutRoute = require('./logout');
|
const logoutRoute = require('./logout');
|
||||||
const migrationRoute = require('./migration');
|
const migrationRoute = require('./migration');
|
||||||
|
const setupRoute = require('./setup');
|
||||||
|
|
||||||
// API routes
|
// API routes
|
||||||
const treeApiRoute = require('./api/tree');
|
const treeApiRoute = require('./api/tree');
|
||||||
@ -19,12 +20,14 @@ const recentNotesRoute = require('./api/recent_notes');
|
|||||||
const appInfoRoute = require('./api/app_info');
|
const appInfoRoute = require('./api/app_info');
|
||||||
const exportRoute = require('./api/export');
|
const exportRoute = require('./api/export');
|
||||||
const importRoute = require('./api/import');
|
const importRoute = require('./api/import');
|
||||||
|
const setupApiRoute = require('./api/setup');
|
||||||
|
|
||||||
function register(app) {
|
function register(app) {
|
||||||
app.use('/', indexRoute);
|
app.use('/', indexRoute);
|
||||||
app.use('/login', loginRoute);
|
app.use('/login', loginRoute);
|
||||||
app.use('/logout', logoutRoute);
|
app.use('/logout', logoutRoute);
|
||||||
app.use('/migration', migrationRoute);
|
app.use('/migration', migrationRoute);
|
||||||
|
app.use('/setup', setupRoute);
|
||||||
|
|
||||||
app.use('/api/tree', treeApiRoute);
|
app.use('/api/tree', treeApiRoute);
|
||||||
app.use('/api/notes', notesApiRoute);
|
app.use('/api/notes', notesApiRoute);
|
||||||
@ -41,6 +44,7 @@ function register(app) {
|
|||||||
app.use('/api/app-info', appInfoRoute);
|
app.use('/api/app-info', appInfoRoute);
|
||||||
app.use('/api/export', exportRoute);
|
app.use('/api/export', exportRoute);
|
||||||
app.use('/api/import', importRoute);
|
app.use('/api/import', importRoute);
|
||||||
|
app.use('/api/setup', setupApiRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
11
routes/setup.js
Normal file
11
routes/setup.js
Normal file
@ -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;
|
92
schema.sql
Normal file
92
schema.sql
Normal file
@ -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
|
||||||
|
);
|
@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
const build = require('./build');
|
const build = require('./build');
|
||||||
const packageJson = require('../package');
|
const packageJson = require('../package');
|
||||||
const migration = require('./migration');
|
|
||||||
|
const APP_DB_VERSION = 49;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
app_version: packageJson.version,
|
app_version: packageJson.version,
|
||||||
db_version: migration.APP_DB_VERSION,
|
db_version: APP_DB_VERSION,
|
||||||
build_date: build.build_date,
|
build_date: build.build_date,
|
||||||
build_revision: build.build_revision
|
build_revision: build.build_revision
|
||||||
};
|
};
|
@ -2,16 +2,22 @@
|
|||||||
|
|
||||||
const migration = require('./migration');
|
const migration = require('./migration');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
const options = require('./options');
|
||||||
|
|
||||||
async function checkAuth(req, res, next) {
|
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");
|
res.redirect("login");
|
||||||
}
|
}
|
||||||
else if (await migration.isDbUpToDate()) {
|
else if (!await migration.isDbUpToDate()) {
|
||||||
next();
|
res.redirect("migration");
|
||||||
}
|
}
|
||||||
else {
|
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 = {
|
module.exports = {
|
||||||
checkAuth,
|
checkAuth,
|
||||||
checkAuthForMigrationPage,
|
checkAuthForMigrationPage,
|
||||||
checkApiAuth,
|
checkApiAuth,
|
||||||
checkApiAuthForMigrationPage
|
checkApiAuthForMigrationPage,
|
||||||
|
checkAppNotInitialized
|
||||||
};
|
};
|
@ -58,10 +58,12 @@ if (!fs.existsSync(dataDir.BACKUP_DIR)) {
|
|||||||
fs.mkdirSync(dataDir.BACKUP_DIR, 0o700);
|
fs.mkdirSync(dataDir.BACKUP_DIR, 0o700);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sql.dbReady.then(() => {
|
||||||
setInterval(regularBackup, 60 * 60 * 1000);
|
setInterval(regularBackup, 60 * 60 * 1000);
|
||||||
|
|
||||||
// kickoff backup immediately
|
// kickoff backup immediately
|
||||||
setTimeout(regularBackup, 1000);
|
setTimeout(regularBackup, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
backupNow
|
backupNow
|
||||||
|
@ -1 +1 @@
|
|||||||
module.exports = { build_date:"2017-11-30T00:11:04-05:00", build_revision: "719f5530544efa1d7aae16afd8a9e64db04ff206" };
|
module.exports = { build_date:"2017-12-06T21:44:45-05:00", build_revision: "4f47c4d6e919aefd303617ac459cea41a1761385" };
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const LOG_DIR = require('./data_dir').LOG_DIR;
|
const data_dir = require('./data_dir');
|
||||||
|
|
||||||
if (!fs.existsSync(LOG_DIR)) {
|
if (!fs.existsSync(data_dir.LOG_DIR)) {
|
||||||
fs.mkdirSync(LOG_DIR, 0o700);
|
fs.mkdirSync(data_dir.LOG_DIR, 0o700);
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = require('simple-node-logger').createRollingFileLogger({
|
const logger = require('simple-node-logger').createRollingFileLogger({
|
||||||
errorEventName: 'error',
|
errorEventName: 'error',
|
||||||
logDirectory: LOG_DIR,
|
logDirectory: data_dir.LOG_DIR,
|
||||||
fileNamePattern: 'trilium-<DATE>.log',
|
fileNamePattern: 'trilium-<DATE>.log',
|
||||||
dateFormat:'YYYY-MM-DD'
|
dateFormat:'YYYY-MM-DD'
|
||||||
});
|
});
|
||||||
@ -37,6 +37,8 @@ function request(req) {
|
|||||||
logger.info(req.method + " " + req.url);
|
logger.info(req.method + " " + req.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info("Using data dir: " + data_dir.TRILIUM_DATA_DIR);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
info,
|
info,
|
||||||
error,
|
error,
|
||||||
|
@ -3,9 +3,15 @@ const sql = require('./sql');
|
|||||||
const options = require('./options');
|
const options = require('./options');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
|
const app_info = require('./app_info');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const APP_DB_VERSION = 48;
|
const MIGRATIONS_DIR = path.resolve(__dirname, "..", "migrations");
|
||||||
const MIGRATIONS_DIR = "migrations";
|
|
||||||
|
if (!fs.existsSync(MIGRATIONS_DIR)) {
|
||||||
|
log.error("Could not find migration directory: " + MIGRATIONS_DIR);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
async function migrate() {
|
async function migrate() {
|
||||||
const migrations = [];
|
const migrations = [];
|
||||||
@ -84,11 +90,10 @@ async function migrate() {
|
|||||||
async function isDbUpToDate() {
|
async function isDbUpToDate() {
|
||||||
const dbVersion = parseInt(await options.getOption('db_version'));
|
const dbVersion = parseInt(await options.getOption('db_version'));
|
||||||
|
|
||||||
return dbVersion >= APP_DB_VERSION;
|
return dbVersion >= app_info.db_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
migrate,
|
migrate,
|
||||||
isDbUpToDate,
|
isDbUpToDate
|
||||||
APP_DB_VERSION
|
|
||||||
};
|
};
|
@ -81,6 +81,12 @@ async function protectNoteRecursively(noteId, dataKey, protect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decryptNote(note, dataKey) {
|
||||||
|
note.note_title = data_encryption.decryptString(dataKey, data_encryption.noteTitleIv(note.note_id), note.note_title);
|
||||||
|
note.note_text = data_encryption.decryptString(dataKey, data_encryption.noteTextIv(note.note_id), note.note_text);
|
||||||
|
note.is_protected = false;
|
||||||
|
}
|
||||||
|
|
||||||
async function protectNote(note, dataKey, protect) {
|
async function protectNote(note, dataKey, protect) {
|
||||||
let changed = false;
|
let changed = false;
|
||||||
|
|
||||||
@ -92,9 +98,7 @@ async function protectNote(note, dataKey, protect) {
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
else if (!protect && note.is_protected) {
|
else if (!protect && note.is_protected) {
|
||||||
note.note_title = data_encryption.decryptString(dataKey, data_encryption.noteTitleIv(note.note_id), note.note_title);
|
decryptNote(note, dataKey);
|
||||||
note.note_text = data_encryption.decryptString(dataKey, data_encryption.noteTextIv(note.note_id), note.note_text);
|
|
||||||
note.is_protected = false;
|
|
||||||
|
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@ -134,9 +138,6 @@ async function protectNoteHistory(noteId, dataKey, protect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateNote(noteId, newNote, ctx) {
|
async function updateNote(noteId, newNote, ctx) {
|
||||||
let noteTitleForHistory = newNote.detail.note_title;
|
|
||||||
let noteTextForHistory = newNote.detail.note_text;
|
|
||||||
|
|
||||||
if (newNote.detail.is_protected) {
|
if (newNote.detail.is_protected) {
|
||||||
await encryptNote(newNote, ctx);
|
await encryptNote(newNote, ctx);
|
||||||
}
|
}
|
||||||
@ -147,19 +148,26 @@ async function updateNote(noteId, newNote, ctx) {
|
|||||||
|
|
||||||
const historyCutoff = now - historySnapshotTimeInterval;
|
const historyCutoff = now - historySnapshotTimeInterval;
|
||||||
|
|
||||||
const existingNoteHistoryId = await sql.getSingleValue("SELECT note_history_id FROM notes_history WHERE note_id = ? AND date_modified_from >= ?", [noteId, historyCutoff]);
|
const existingNoteHistoryId = await sql.getSingleValue("SELECT note_history_id FROM notes_history WHERE note_id = ? AND date_modified_to >= ?", [noteId, historyCutoff]);
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.doInTransaction(async () => {
|
||||||
if (!existingNoteHistoryId && (now - newNote.detail.date_created) >= historySnapshotTimeInterval) {
|
if (!existingNoteHistoryId && (now - newNote.detail.date_created) >= historySnapshotTimeInterval) {
|
||||||
|
const oldNote = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteId]);
|
||||||
|
|
||||||
|
if (oldNote.is_protected) {
|
||||||
|
decryptNote(oldNote, ctx.getDataKey());
|
||||||
|
}
|
||||||
|
|
||||||
const newNoteHistoryId = utils.newNoteHistoryId();
|
const newNoteHistoryId = utils.newNoteHistoryId();
|
||||||
|
|
||||||
await sql.insert('notes_history', {
|
await sql.insert('notes_history', {
|
||||||
note_history_id: newNoteHistoryId,
|
note_history_id: newNoteHistoryId,
|
||||||
note_id: noteId,
|
note_id: noteId,
|
||||||
note_title: noteTitleForHistory,
|
// title and text should be decrypted now
|
||||||
note_text: noteTextForHistory,
|
note_title: oldNote.note_title,
|
||||||
is_protected: false, // we don't care about encryption - this will be handled in protectNoteHistory()
|
note_text: oldNote.note_text,
|
||||||
date_modified_from: now,
|
is_protected: 0, // will be fixed in the protectNoteHistory() call
|
||||||
|
date_modified_from: oldNote.date_modified,
|
||||||
date_modified_to: now
|
date_modified_to: now
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const sync_table = require('./sync_table');
|
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',
|
const SYNCED_OPTIONS = [ 'username', 'password_verification_hash', 'encrypted_data_key', 'protected_session_timeout',
|
||||||
'history_snapshot_time_interval' ];
|
'history_snapshot_time_interval' ];
|
||||||
@ -20,21 +21,38 @@ async function setOption(optName, optValue) {
|
|||||||
await sync_table.addOptionsSync(optName);
|
await sync_table.addOptionsSync(optName);
|
||||||
}
|
}
|
||||||
|
|
||||||
await sql.execute("UPDATE options SET opt_value = ?, date_modified = ? WHERE opt_name = ?",
|
await sql.replace("options", {
|
||||||
[optValue, utils.nowTimestamp(), optName]);
|
opt_name: optName,
|
||||||
|
opt_value: optValue,
|
||||||
|
date_modified: utils.nowTimestamp()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sql.dbReady.then(async () => {
|
async function initOptions(startNotePath) {
|
||||||
if (!await getOption('document_id') || !await getOption('document_secret')) {
|
|
||||||
await sql.doInTransaction(async () => {
|
|
||||||
await setOption('document_id', utils.randomSecureToken(16));
|
await setOption('document_id', utils.randomSecureToken(16));
|
||||||
await setOption('document_secret', 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_path', startNotePath);
|
||||||
|
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 = {
|
module.exports = {
|
||||||
getOption,
|
getOption,
|
||||||
setOption,
|
setOption,
|
||||||
|
initOptions,
|
||||||
SYNCED_OPTIONS
|
SYNCED_OPTIONS
|
||||||
};
|
};
|
@ -8,7 +8,7 @@ const sync = require('./sync');
|
|||||||
let startTime = utils.nowTimestamp();
|
let startTime = utils.nowTimestamp();
|
||||||
let sentSyncId = [];
|
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]);
|
const syncs = await sql.getResults("SELECT * FROM sync WHERE sync_date >= ? AND source_id != ?", [startTime, source_id.currentSourceId]);
|
||||||
startTime = utils.nowTimestamp();
|
startTime = utils.nowTimestamp();
|
||||||
|
|
||||||
@ -41,4 +41,6 @@ setInterval(async () => {
|
|||||||
for (const syncId of syncIds) {
|
for (const syncId of syncIds) {
|
||||||
sentSyncId.push(syncId);
|
sentSyncId.push(syncId);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}
|
||||||
|
|
||||||
|
sql.dbReady.then(() => setInterval(sendPing, 1000));
|
@ -2,13 +2,74 @@
|
|||||||
|
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
const dataDir = require('./data_dir');
|
const dataDir = require('./data_dir');
|
||||||
|
const fs = require('fs');
|
||||||
const sqlite = require('sqlite');
|
const sqlite = require('sqlite');
|
||||||
|
const utils = require('./utils');
|
||||||
|
|
||||||
async function createConnection() {
|
async function createConnection() {
|
||||||
return await sqlite.open(dataDir.DOCUMENT_PATH, {Promise});
|
return await sqlite.open(dataDir.DOCUMENT_PATH, {Promise});
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbReady = 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) {
|
||||||
|
log.info("Connected to db, but schema doesn't exist. Initializing schema ...");
|
||||||
|
|
||||||
|
const schema = fs.readFileSync('schema.sql', 'UTF-8');
|
||||||
|
|
||||||
|
await doInTransaction(async () => {
|
||||||
|
await executeScript(schema);
|
||||||
|
|
||||||
|
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
|
||||||
|
// 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);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setDbReadyAsResolved() {
|
||||||
|
dbReadyResolve();
|
||||||
|
}
|
||||||
|
|
||||||
async function insert(table_name, rec, replace = false) {
|
async function insert(table_name, rec, replace = false) {
|
||||||
const keys = Object.keys(rec);
|
const keys = Object.keys(rec);
|
||||||
@ -44,13 +105,10 @@ async function rollback() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getSingleResult(query, params = []) {
|
async function getSingleResult(query, params = []) {
|
||||||
const db = await dbReady;
|
|
||||||
|
|
||||||
return await wrap(async db => db.get(query, ...params));
|
return await wrap(async db => db.get(query, ...params));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSingleResultOrNull(query, params = []) {
|
async function getSingleResultOrNull(query, params = []) {
|
||||||
const db = await dbReady;
|
|
||||||
const all = await wrap(async db => db.all(query, ...params));
|
const all = await wrap(async db => db.all(query, ...params));
|
||||||
|
|
||||||
return all.length > 0 ? all[0] : null;
|
return all.length > 0 ? all[0] : null;
|
||||||
@ -67,8 +125,6 @@ async function getSingleValue(query, params = []) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getResults(query, params = []) {
|
async function getResults(query, params = []) {
|
||||||
const db = await dbReady;
|
|
||||||
|
|
||||||
return await wrap(async db => db.all(query, ...params));
|
return await wrap(async db => db.all(query, ...params));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +162,7 @@ async function executeScript(query) {
|
|||||||
|
|
||||||
async function wrap(func) {
|
async function wrap(func) {
|
||||||
const thisError = new Error();
|
const thisError = new Error();
|
||||||
const db = await dbReady;
|
const db = await dbConnected;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await func(db);
|
return await func(db);
|
||||||
@ -157,20 +213,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 = {
|
module.exports = {
|
||||||
dbReady,
|
dbReady,
|
||||||
insert,
|
insert,
|
||||||
@ -183,5 +225,6 @@ module.exports = {
|
|||||||
getFlattenedResults,
|
getFlattenedResults,
|
||||||
execute,
|
execute,
|
||||||
executeScript,
|
executeScript,
|
||||||
doInTransaction
|
doInTransaction,
|
||||||
|
setDbReadyAsResolved
|
||||||
};
|
};
|
@ -13,6 +13,7 @@ const syncUpdate = require('./sync_update');
|
|||||||
const content_hash = require('./content_hash');
|
const content_hash = require('./content_hash');
|
||||||
const event_log = require('./event_log');
|
const event_log = require('./event_log');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const app_info = require('./app_info');
|
||||||
|
|
||||||
const SYNC_SERVER = config['Sync']['syncServerHost'];
|
const SYNC_SERVER = config['Sync']['syncServerHost'];
|
||||||
const isSyncSetup = !!SYNC_SERVER;
|
const isSyncSetup = !!SYNC_SERVER;
|
||||||
@ -94,7 +95,7 @@ async function login() {
|
|||||||
|
|
||||||
const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', {
|
const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', {
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
dbVersion: migration.APP_DB_VERSION,
|
dbVersion: app_info.db_version,
|
||||||
hash: hash
|
hash: hash
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -305,6 +306,7 @@ async function syncRequest(syncContext, method, uri, body) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sql.dbReady.then(() => {
|
||||||
if (isSyncSetup) {
|
if (isSyncSetup) {
|
||||||
log.info("Setting up sync to " + SYNC_SERVER + " with timeout " + SYNC_TIMEOUT);
|
log.info("Setting up sync to " + SYNC_SERVER + " with timeout " + SYNC_TIMEOUT);
|
||||||
|
|
||||||
@ -328,6 +330,7 @@ if (isSyncSetup) {
|
|||||||
else {
|
else {
|
||||||
log.info("Sync server not configured, sync timer not running.")
|
log.info("Sync server not configured, sync timer not running.")
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sync,
|
sync,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
function newNoteId() {
|
function newNoteId() {
|
||||||
return randomString(8);
|
return randomString(12);
|
||||||
}
|
}
|
||||||
|
|
||||||
function newNoteTreeId() {
|
function newNoteTreeId() {
|
||||||
@ -14,16 +14,10 @@ function newNoteHistoryId() {
|
|||||||
return randomString(12);
|
return randomString(12);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALPHA_NUMERIC = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
||||||
|
|
||||||
function randomString(length) {
|
function randomString(length) {
|
||||||
let result = '';
|
const token = randomSecureToken(32);
|
||||||
|
|
||||||
for (let i = length; i > 0; --i) {
|
return token.substr(0, length);
|
||||||
result += ALPHA_NUMERIC[Math.floor(Math.random() * ALPHA_NUMERIC.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomSecureToken(bytes = 32) {
|
function randomSecureToken(bytes = 32) {
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hide-toggle" style="grid-area: tree-actions">
|
<div class="hide-toggle" style="grid-area: tree-actions;">
|
||||||
|
<div style="display: flex; justify-content: space-evenly; padding: 10px 0 10px 0; margin: 0 20px 0 20px; border: 1px solid #ccc;">
|
||||||
<a onclick="noteTree.createNewTopLevelNote()" title="Create new top level note" class="icon-action">
|
<a onclick="noteTree.createNewTopLevelNote()" title="Create new top level note" class="icon-action">
|
||||||
<img src="images/icons/file-plus.png" alt="Create new top level note"/>
|
<img src="images/icons/file-plus.png" alt="Create new top level note"/>
|
||||||
</a>
|
</a>
|
||||||
@ -49,6 +50,7 @@
|
|||||||
<a onclick="searchTree.toggleSearch()" title="Search in notes" class="icon-action">
|
<a onclick="searchTree.toggleSearch()" title="Search in notes" class="icon-action">
|
||||||
<img src="images/icons/search.png" alt="Search in notes"/>
|
<img src="images/icons/search.png" alt="Search in notes"/>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="search-box" style="display: none; padding: 10px; margin-top: 10px;">
|
<div id="search-box" style="display: none; padding: 10px; margin-top: 10px;">
|
||||||
<p>
|
<p>
|
||||||
@ -63,7 +65,7 @@
|
|||||||
<div id="tree" class="hide-toggle" style="grid-area: tree; overflow: auto;">
|
<div id="tree" class="hide-toggle" style="grid-area: tree; overflow: auto;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="parent-list" style="display: none;">
|
<div id="parent-list">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hide-toggle" style="grid-area: title;">
|
<div class="hide-toggle" style="grid-area: title;">
|
||||||
|
48
views/setup.ejs
Normal file
48
views/setup.ejs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Setup</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="width: 500px; margin: auto;">
|
||||||
|
<h1>Trilium setup</h1>
|
||||||
|
|
||||||
|
<div class="alert alert-warning" id="alert" style="display: none;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="setup-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" class="form-control" id="username" placeholder="Arbitrary string">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password1">Password</label>
|
||||||
|
<input type="password" class="form-control" id="password1" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password2">Repeat password</label>
|
||||||
|
<input type="password" class="form-control" id="password2" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-default">Save</button>
|
||||||
|
</form>
|
||||||
|
</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;}</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/setup.js"></script>
|
||||||
|
<script src="javascripts/utils.js"></script>
|
||||||
|
<script src="javascripts/server.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user