mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 09:58:32 +02:00
#98, working sync setup from server to desktop instance + refactoring of DB initialization
This commit is contained in:
parent
a201661ce5
commit
073300bbcd
@ -1,4 +1,3 @@
|
|||||||
import server from './services/server.js';
|
|
||||||
import utils from "./services/utils.js";
|
import utils from "./services/utils.js";
|
||||||
|
|
||||||
function SetupModel() {
|
function SetupModel() {
|
||||||
@ -56,7 +55,8 @@ function SetupModel() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
server.post('setup', {
|
// not using server.js because it loads too many dependencies
|
||||||
|
$.post('/api/setup/new-document', {
|
||||||
username: username,
|
username: username,
|
||||||
password: password1
|
password: password1
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -83,7 +83,17 @@ function SetupModel() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showAlert("All OK");
|
// not using server.js because it loads too many dependencies
|
||||||
|
$.post('/api/setup/sync-from-server', {
|
||||||
|
serverAddress: serverAddress,
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
}).then(() => {
|
||||||
|
window.location.replace("/");
|
||||||
|
}).catch((err) => {
|
||||||
|
alert("Error, see dev console for details.");
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,74 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sqlInit = require('../../services/sql_init');
|
const sqlInit = require('../../services/sql_init');
|
||||||
|
const sql = require('../../services/sql');
|
||||||
|
const cls = require('../../services/cls');
|
||||||
|
const tmp = require('tmp-promise');
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const log = require('../../services/log');
|
||||||
|
const DOCUMENT_PATH = require('../../services/data_dir').DOCUMENT_PATH;
|
||||||
|
const sourceIdService = require('../../services/source_id');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
async function setup(req) {
|
async function setupNewDocument(req) {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
await sqlInit.createInitialDatabase(username, password);
|
await sqlInit.createInitialDatabase(username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setupSyncFromServer(req) {
|
||||||
|
const { serverAddress, username, password } = req.body;
|
||||||
|
|
||||||
|
const tempFile = await tmp.file();
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const file = fs.createWriteStream(tempFile.path);
|
||||||
|
const parsedAddress = url.parse(serverAddress);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'GET',
|
||||||
|
protocol: parsedAddress.protocol,
|
||||||
|
host: parsedAddress.hostname,
|
||||||
|
port: parsedAddress.port,
|
||||||
|
path: '/api/sync/document',
|
||||||
|
auth: username + ':' + password
|
||||||
|
};
|
||||||
|
|
||||||
|
log.info("Getting document from: " + serverAddress + JSON.stringify(options));
|
||||||
|
|
||||||
|
http.request(options, function(response) {
|
||||||
|
response.pipe(file);
|
||||||
|
|
||||||
|
file.on('finish', function() {
|
||||||
|
log.info("Document download finished, closing & renaming.");
|
||||||
|
|
||||||
|
file.close(() => { // close() is async, call after close completes.
|
||||||
|
fs.rename(tempFile.path, DOCUMENT_PATH, async () => {
|
||||||
|
cls.reset();
|
||||||
|
|
||||||
|
await sqlInit.initDbConnection();
|
||||||
|
|
||||||
|
// we need to generate new source ID for this instance, otherwise it will
|
||||||
|
// match the original server one
|
||||||
|
await sql.transactional(async () => {
|
||||||
|
await sourceIdService.generateSourceId();
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).on('error', function(err) { // Handle errors
|
||||||
|
fs.unlink(tempFile.path); // Delete the file async. (But we don't check the result)
|
||||||
|
|
||||||
|
reject(err.message);
|
||||||
|
log.error(err.message);
|
||||||
|
}).end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
setup
|
setupNewDocument,
|
||||||
|
setupSyncFromServer
|
||||||
};
|
};
|
@ -7,6 +7,7 @@ const sql = require('../../services/sql');
|
|||||||
const optionService = require('../../services/options');
|
const optionService = require('../../services/options');
|
||||||
const contentHashService = require('../../services/content_hash');
|
const contentHashService = require('../../services/content_hash');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
|
const DOCUMENT_PATH = require('../../services/data_dir').DOCUMENT_PATH;
|
||||||
|
|
||||||
async function checkSync() {
|
async function checkSync() {
|
||||||
return {
|
return {
|
||||||
@ -72,6 +73,12 @@ async function update(req) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getDocument(req, resp) {
|
||||||
|
log.info("Serving document.");
|
||||||
|
|
||||||
|
resp.sendFile(DOCUMENT_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
checkSync,
|
checkSync,
|
||||||
syncNow,
|
syncNow,
|
||||||
@ -79,5 +86,6 @@ module.exports = {
|
|||||||
forceFullSync,
|
forceFullSync,
|
||||||
forceNoteSync,
|
forceNoteSync,
|
||||||
getChanged,
|
getChanged,
|
||||||
update
|
update,
|
||||||
|
getDocument
|
||||||
};
|
};
|
@ -62,16 +62,21 @@ function apiRoute(method, path, routeHandler) {
|
|||||||
route(method, path, [auth.checkApiAuth], routeHandler, apiResultHandler);
|
route(method, path, [auth.checkApiAuth], routeHandler, apiResultHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
function route(method, path, middleware, routeHandler, resultHandler) {
|
function route(method, path, middleware, routeHandler, resultHandler, transactional = true) {
|
||||||
router[method](path, ...middleware, async (req, res, next) => {
|
router[method](path, ...middleware, async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const result = await cls.init(async () => {
|
const result = await cls.init(async () => {
|
||||||
cls.namespace.set('sourceId', req.headers.source_id);
|
cls.namespace.set('sourceId', req.headers.source_id);
|
||||||
protectedSessionService.setProtectedSessionId(req);
|
protectedSessionService.setProtectedSessionId(req);
|
||||||
|
|
||||||
return await sql.transactional(async () => {
|
if (transactional) {
|
||||||
|
return await sql.transactional(async () => {
|
||||||
|
return await routeHandler(req, res, next);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
return await routeHandler(req, res, next);
|
return await routeHandler(req, res, next);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resultHandler) {
|
if (resultHandler) {
|
||||||
@ -149,6 +154,7 @@ function register(app) {
|
|||||||
apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
|
apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
|
||||||
apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged);
|
apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged);
|
||||||
apiRoute(PUT, '/api/sync/update', syncApiRoute.update);
|
apiRoute(PUT, '/api/sync/update', syncApiRoute.update);
|
||||||
|
route(GET, '/api/sync/document', [auth.checkBasicAuth], syncApiRoute.getDocument);
|
||||||
|
|
||||||
apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog);
|
apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog);
|
||||||
|
|
||||||
@ -156,7 +162,8 @@ function register(app) {
|
|||||||
apiRoute(PUT, '/api/recent-notes/:branchId/:notePath', recentNotesRoute.addRecentNote);
|
apiRoute(PUT, '/api/recent-notes/:branchId/:notePath', recentNotesRoute.addRecentNote);
|
||||||
apiRoute(GET, '/api/app-info', appInfoRoute.getAppInfo);
|
apiRoute(GET, '/api/app-info', appInfoRoute.getAppInfo);
|
||||||
|
|
||||||
route(POST, '/api/setup', [auth.checkAppNotInitialized], setupApiRoute.setup, apiResultHandler);
|
route(POST, '/api/setup/new-document', [auth.checkAppNotInitialized], setupApiRoute.setupNewDocument, apiResultHandler);
|
||||||
|
route(POST, '/api/setup/sync-from-server', [auth.checkAppNotInitialized], setupApiRoute.setupSyncFromServer, apiResultHandler, false);
|
||||||
|
|
||||||
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
||||||
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
|
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const migrationService = require('./migration');
|
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const sqlInit = require('./sql_init');
|
const sqlInit = require('./sql_init');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
const passwordEncryptionService = require('./password_encryption');
|
||||||
|
const optionService = require('./options');
|
||||||
|
|
||||||
async function checkAuth(req, res, next) {
|
async function checkAuth(req, res, next) {
|
||||||
if (!await sqlInit.isUserInitialized()) {
|
if (!await sqlInit.isDbInitialized()) {
|
||||||
res.redirect("setup");
|
res.redirect("setup");
|
||||||
}
|
}
|
||||||
else if (!req.session.loggedIn && !utils.isElectron()) {
|
else if (!req.session.loggedIn && !utils.isElectron()) {
|
||||||
@ -38,7 +39,7 @@ async function checkApiAuth(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkAppNotInitialized(req, res, next) {
|
async function checkAppNotInitialized(req, res, next) {
|
||||||
if (await sqlInit.isUserInitialized()) {
|
if (await sqlInit.isDbInitialized()) {
|
||||||
res.status(400).send("App already initialized.");
|
res.status(400).send("App already initialized.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -57,10 +58,27 @@ async function checkSenderToken(req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkBasicAuth(req, res, next) {
|
||||||
|
const header = req.headers.authorization || '';
|
||||||
|
const token = header.split(/\s+/).pop() || '';
|
||||||
|
const auth = new Buffer.from(token, 'base64').toString();
|
||||||
|
const [username, password] = auth.split(/:/);
|
||||||
|
|
||||||
|
const dbUsername = await optionService.getOption('username');
|
||||||
|
|
||||||
|
if (dbUsername !== username || !await passwordEncryptionService.verifyPassword(password)) {
|
||||||
|
res.status(401).send("Not authorized");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
checkAuth,
|
checkAuth,
|
||||||
checkApiAuth,
|
checkApiAuth,
|
||||||
checkAppNotInitialized,
|
checkAppNotInitialized,
|
||||||
checkApiAuthOrElectron,
|
checkApiAuthOrElectron,
|
||||||
checkSenderToken
|
checkSenderToken,
|
||||||
|
checkBasicAuth
|
||||||
};
|
};
|
@ -13,9 +13,14 @@ function getSourceId() {
|
|||||||
return namespace.get('sourceId');
|
return namespace.get('sourceId');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
clsHooked.reset();
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init,
|
init,
|
||||||
wrap,
|
wrap,
|
||||||
namespace,
|
namespace,
|
||||||
getSourceId
|
getSourceId,
|
||||||
|
reset
|
||||||
};
|
};
|
@ -86,7 +86,7 @@ async function migrate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sqlInit.isDbUpToDate()) {
|
if (sqlInit.isDbUpToDate()) {
|
||||||
sqlInit.setDbReadyAsResolved();
|
await sqlInit.initDbConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
return migrations;
|
return migrations;
|
||||||
|
@ -157,10 +157,10 @@ async function transactional(func) {
|
|||||||
transactionActive = true;
|
transactionActive = true;
|
||||||
transactionPromise = new Promise(async (resolve, reject) => {
|
transactionPromise = new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
cls.namespace.set('isInTransaction', true);
|
|
||||||
|
|
||||||
await beginTransaction();
|
await beginTransaction();
|
||||||
|
|
||||||
|
cls.namespace.set('isInTransaction', true);
|
||||||
|
|
||||||
ret = await func();
|
ret = await func();
|
||||||
|
|
||||||
await commit();
|
await commit();
|
||||||
|
@ -11,38 +11,32 @@ async function createConnection() {
|
|||||||
return await sqlite.open(dataDir.DOCUMENT_PATH, {Promise});
|
return await sqlite.open(dataDir.DOCUMENT_PATH, {Promise});
|
||||||
}
|
}
|
||||||
|
|
||||||
let schemaReadyResolve = null;
|
|
||||||
const schemaReady = new Promise((resolve, reject) => schemaReadyResolve = resolve);
|
|
||||||
|
|
||||||
let dbReadyResolve = null;
|
let dbReadyResolve = null;
|
||||||
const dbReady = new Promise((resolve, reject) => {
|
const dbReady = new Promise((resolve, reject) => {
|
||||||
cls.init(async () => {
|
dbReadyResolve = resolve;
|
||||||
|
|
||||||
|
initDbConnection();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function isDbInitialized() {
|
||||||
|
const tableResults = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'");
|
||||||
|
|
||||||
|
return tableResults.length === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initDbConnection() {
|
||||||
|
await cls.init(async () => {
|
||||||
const db = await createConnection();
|
const db = await createConnection();
|
||||||
sql.setDbConnection(db);
|
sql.setDbConnection(db);
|
||||||
|
|
||||||
await sql.execute("PRAGMA foreign_keys = ON");
|
await sql.execute("PRAGMA foreign_keys = ON");
|
||||||
|
|
||||||
dbReadyResolve = () => {
|
if (isDbInitialized()) {
|
||||||
log.info("DB ready.");
|
|
||||||
|
|
||||||
resolve(db);
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableResults = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='notes'");
|
|
||||||
if (tableResults.length !== 1) {
|
|
||||||
log.info("DB not found, please visit setup page to initialize Trilium.");
|
log.info("DB not found, please visit setup page to initialize Trilium.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
schemaReadyResolve();
|
|
||||||
|
|
||||||
if (!await isUserInitialized()) {
|
|
||||||
log.info("Login/password not initialized. DB not ready.");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await isDbUpToDate()) {
|
if (!await isDbUpToDate()) {
|
||||||
// avoiding circular dependency
|
// avoiding circular dependency
|
||||||
const migrationService = require('./migration');
|
const migrationService = require('./migration');
|
||||||
@ -50,9 +44,10 @@ const dbReady = new Promise((resolve, reject) => {
|
|||||||
await migrationService.migrate();
|
await migrationService.migrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(db);
|
log.info("DB ready.");
|
||||||
|
dbReadyResolve(db);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
async function createInitialDatabase(username, password) {
|
async function createInitialDatabase(username, password) {
|
||||||
log.info("Connected to db, but schema doesn't exist. Initializing schema ...");
|
log.info("Connected to db, but schema doesn't exist. Initializing schema ...");
|
||||||
@ -78,11 +73,7 @@ async function createInitialDatabase(username, password) {
|
|||||||
|
|
||||||
log.info("Schema and initial content generated. Waiting for user to enter username/password to finish setup.");
|
log.info("Schema and initial content generated. Waiting for user to enter username/password to finish setup.");
|
||||||
|
|
||||||
setDbReadyAsResolved();
|
await initDbConnection();
|
||||||
}
|
|
||||||
|
|
||||||
function setDbReadyAsResolved() {
|
|
||||||
dbReadyResolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isDbUpToDate() {
|
async function isDbUpToDate() {
|
||||||
@ -97,23 +88,10 @@ async function isDbUpToDate() {
|
|||||||
return upToDate;
|
return upToDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isUserInitialized() {
|
|
||||||
const optionsTable = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='options'");
|
|
||||||
|
|
||||||
if (optionsTable.length !== 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const username = await sql.getValue("SELECT value FROM options WHERE name = 'username'");
|
|
||||||
|
|
||||||
return !!username;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
dbReady,
|
dbReady,
|
||||||
schemaReady,
|
isDbInitialized,
|
||||||
isUserInitialized,
|
initDbConnection,
|
||||||
setDbReadyAsResolved,
|
|
||||||
isDbUpToDate,
|
isDbUpToDate,
|
||||||
createInitialDatabase
|
createInitialDatabase
|
||||||
};
|
};
|
@ -28,7 +28,7 @@ async function setUserNamePassword() {
|
|||||||
|
|
||||||
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16));
|
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16));
|
||||||
|
|
||||||
sqlInit.setDbReadyAsResolved();
|
await sqlInit.initDbConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteCount = parseInt(process.argv[2]);
|
const noteCount = parseInt(process.argv[2]);
|
||||||
@ -71,4 +71,4 @@ async function start() {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlInit.schemaReady.then(cls.wrap(start));
|
sqlInit.dbReady.then(cls.wrap(start));
|
Loading…
x
Reference in New Issue
Block a user