#12: Remove Electron-specific code in db initialization

This commit is contained in:
Elian Doran 2024-05-12 11:29:44 +03:00
parent 780f2577c8
commit 80151c2b2f
No known key found for this signature in database
12 changed files with 109 additions and 85 deletions

View File

@ -81,6 +81,10 @@ const app = startTrilium({
setupCompleteCallback: () => { setupCompleteCallback: () => {
windowService.createMainWindow(electron.app); windowService.createMainWindow(electron.app);
windowService.closeSetupWindow(); windowService.closeSetupWindow();
},
getInitialTheme() {
return electron.nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
} }
}); });
electronRouting(app); electronRouting(app);

View File

@ -6,9 +6,9 @@ import helmet = require('helmet');
import compression = require('compression'); import compression = require('compression');
import sessionParser = require('./routes/session_parser'); import sessionParser = require('./routes/session_parser');
import utils = require('./services/utils'); import utils = require('./services/utils');
import { RouteConfig } from './routes/types'; import { AppConfig } from './types';
function buildApp(routeConfig: RouteConfig) { function buildApp(appConfig: AppConfig) {
require('./services/handlers'); require('./services/handlers');
require('./becca/becca_loader'); require('./becca/becca_loader');
@ -40,7 +40,7 @@ function buildApp(routeConfig: RouteConfig) {
app.use(favicon(`${__dirname}/../../common/images/app-icons/win/icon.ico`)); app.use(favicon(`${__dirname}/../../common/images/app-icons/win/icon.ico`));
require('./routes/assets').register(app); require('./routes/assets').register(app);
require('./routes/routes').register(app, routeConfig); require('./routes/routes').register(app, appConfig);
require('./routes/custom').register(app); require('./routes/custom').register(app);
require('./routes/error_handlers').register(app); require('./routes/error_handlers').register(app);

View File

@ -3,5 +3,10 @@ import startTrilium = require("./www");
startTrilium({ startTrilium({
setupCompleteCallback: (res) => { setupCompleteCallback: (res) => {
res.redirect('.'); res.redirect('.');
},
getInitialTheme() {
// default based on the poll in https://github.com/zadam/trilium/issues/2516
return "dark";
} }
}); });

View File

@ -5,56 +5,61 @@ import setupService = require('../../services/setup');
import log = require('../../services/log'); import log = require('../../services/log');
import appInfo = require('../../services/app_info'); import appInfo = require('../../services/app_info');
import { Request } from 'express'; import { Request } from 'express';
import { InitDbOptions } from '../../types';
function getStatus() { function buildRoutes(initOptions: InitDbOptions) {
return { function getStatus() {
isInitialized: sqlInit.isDbInitialized(), return {
schemaExists: sqlInit.schemaExists(), isInitialized: sqlInit.isDbInitialized(),
syncVersion: appInfo.syncVersion schemaExists: sqlInit.schemaExists(),
}; syncVersion: appInfo.syncVersion
} };
async function setupNewDocument() {
await sqlInit.createInitialDatabase();
}
function setupSyncFromServer(req: Request) {
const { syncServerHost, syncProxy, password } = req.body;
return setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, password);
}
function saveSyncSeed(req: Request) {
const { options, syncVersion } = req.body;
if (appInfo.syncVersion !== syncVersion) {
const message = `Could not setup sync since local sync protocol version is ${appInfo.syncVersion} while remote is ${syncVersion}. To fix this issue, use same Trilium version on all instances.`;
log.error(message);
return [400, {
error: message
}]
} }
log.info("Saved sync seed."); async function setupNewDocument() {
await sqlInit.createInitialDatabase(initOptions);
}
sqlInit.createDatabaseForSync(options); function setupSyncFromServer(req: Request) {
} const { syncServerHost, syncProxy, password } = req.body;
function getSyncSeed() { return setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, password, initOptions);
log.info("Serving sync seed."); }
function saveSyncSeed(req: Request) {
const { options, syncVersion } = req.body;
if (appInfo.syncVersion !== syncVersion) {
const message = `Could not setup sync since local sync protocol version is ${appInfo.syncVersion} while remote is ${syncVersion}. To fix this issue, use same Trilium version on all instances.`;
log.error(message);
return [400, {
error: message
}]
}
log.info("Saved sync seed.");
sqlInit.createDatabaseForSync(options, initOptions);
}
function getSyncSeed() {
log.info("Serving sync seed.");
return {
options: setupService.getSyncSeedOptions(),
syncVersion: appInfo.syncVersion
};
}
return { return {
options: setupService.getSyncSeedOptions(), getStatus,
syncVersion: appInfo.syncVersion setupNewDocument,
setupSyncFromServer,
getSyncSeed,
saveSyncSeed
}; };
} }
export = { export = buildRoutes;
getStatus,
setupNewDocument,
setupSyncFromServer,
getSyncSeed,
saveSyncSeed
};

View File

@ -38,7 +38,7 @@ import recentNotesRoute = require('./api/recent_notes');
import appInfoRoute = require('./api/app_info'); import appInfoRoute = require('./api/app_info');
import exportRoute = require('./api/export'); import exportRoute = require('./api/export');
import importRoute = require('./api/import'); import importRoute = require('./api/import');
import setupApiRoute = require('./api/setup'); import buildSetupRoute = require('./api/setup');
import sqlRoute = require('./api/sql'); import sqlRoute = require('./api/sql');
import databaseRoute = require('./api/database'); import databaseRoute = require('./api/database');
import imageRoute = require('./api/image'); import imageRoute = require('./api/image');
@ -71,7 +71,7 @@ import etapiSpecialNoteRoutes = require('../etapi/special_notes');
import etapiSpecRoute = require('../etapi/spec'); import etapiSpecRoute = require('../etapi/spec');
import etapiBackupRoute = require('../etapi/backup'); import etapiBackupRoute = require('../etapi/backup');
import { AppRequest, AppRequestHandler } from './route-interface'; import { AppRequest, AppRequestHandler } from './route-interface';
import { RouteConfig } from './types'; import { AppConfig } from '../types';
const csrfMiddleware = csurf({ const csrfMiddleware = csurf({
cookie: { cookie: {
@ -102,7 +102,7 @@ const uploadMiddlewareWithErrorHandling = function (req: express.Request, res: e
}); });
}; };
function register(app: express.Application, config: RouteConfig) { function register(app: express.Application, appConfig: AppConfig) {
route(GET, '/', [auth.checkAuth, csrfMiddleware], indexRoute.index); route(GET, '/', [auth.checkAuth, csrfMiddleware], indexRoute.index);
route(GET, '/login', [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage); route(GET, '/login', [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage);
route(GET, '/set-password', [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage); route(GET, '/set-password', [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage);
@ -116,7 +116,7 @@ function register(app: express.Application, config: RouteConfig) {
route(PST, '/login', [loginRateLimiter], loginRoute.login); route(PST, '/login', [loginRateLimiter], loginRoute.login);
route(PST, '/logout', [csrfMiddleware, auth.checkAuth], loginRoute.logout); route(PST, '/logout', [csrfMiddleware, auth.checkAuth], loginRoute.logout);
route(PST, '/set-password', [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPassword); route(PST, '/set-password', [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPassword);
route(GET, '/setup', [], setupRoute.buildSetupRoute(config.setupCompleteCallback)); route(GET, '/setup', [], setupRoute.buildSetupRoute(appConfig.setupCompleteCallback));
apiRoute(GET, '/api/tree', treeApiRoute.getTree); apiRoute(GET, '/api/tree', treeApiRoute.getTree);
apiRoute(PST, '/api/tree/load', treeApiRoute.load); apiRoute(PST, '/api/tree/load', treeApiRoute.load);
@ -241,6 +241,7 @@ function register(app: express.Application, config: RouteConfig) {
route(GET, '/api/health-check', [], () => ({ "status": "ok" }), apiResultHandler); route(GET, '/api/health-check', [], () => ({ "status": "ok" }), apiResultHandler);
// group of the services below are meant to be executed from the outside // group of the services below are meant to be executed from the outside
const setupApiRoute = buildSetupRoute(appConfig);
route(GET, '/api/setup/status', [], setupApiRoute.getStatus, apiResultHandler); route(GET, '/api/setup/status', [], setupApiRoute.getStatus, apiResultHandler);
route(PST, '/api/setup/new-document', [auth.checkAppNotInitialized], setupApiRoute.setupNewDocument, apiResultHandler, false); route(PST, '/api/setup/new-document', [auth.checkAppNotInitialized], setupApiRoute.setupNewDocument, apiResultHandler, false);
route(PST, '/api/setup/sync-from-server', [auth.checkAppNotInitialized], setupApiRoute.setupSyncFromServer, apiResultHandler, false); route(PST, '/api/setup/sync-from-server', [auth.checkAppNotInitialized], setupApiRoute.setupSyncFromServer, apiResultHandler, false);

View File

@ -5,7 +5,7 @@ import setupService = require('../services/setup');
import assetPath = require('../services/asset_path'); import assetPath = require('../services/asset_path');
import appPath = require('../services/app_path'); import appPath = require('../services/app_path');
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { SetupCompleteCallback } from './types'; import { SetupCompleteCallback } from '../types';
function buildSetupRoute(setupCompleteCallback: SetupCompleteCallback) { function buildSetupRoute(setupCompleteCallback: SetupCompleteCallback) {
return (req: Request, res: Response) => { return (req: Request, res: Response) => {

View File

@ -1,8 +0,0 @@
import { Response } from "express";
export type SetupCompleteCallback = (res: Response) => void;
export interface RouteConfig {
/** Callback to be invoked when first setup is complete. */
setupCompleteCallback: SetupCompleteCallback;
}

View File

@ -5,6 +5,7 @@ import log = require('./log');
import dateUtils = require('./date_utils'); import dateUtils = require('./date_utils');
import keyboardActions = require('./keyboard_actions'); import keyboardActions = require('./keyboard_actions');
import { KeyboardShortcutWithRequiredActionName } from './keyboard_actions_interface'; import { KeyboardShortcutWithRequiredActionName } from './keyboard_actions_interface';
import { InitDbOptions } from '../types';
function initDocumentOptions() { function initDocumentOptions() {
optionService.createOption('documentId', utils.randomSecureToken(16), false); optionService.createOption('documentId', utils.randomSecureToken(16), false);
@ -16,7 +17,7 @@ interface NotSyncedOpts {
syncProxy?: string; syncProxy?: string;
} }
function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}) { function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}, initOptions: InitDbOptions) {
optionService.createOption('openNoteContexts', JSON.stringify([ optionService.createOption('openNoteContexts', JSON.stringify([
{ {
notePath: 'root', notePath: 'root',
@ -34,15 +35,7 @@ function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}) {
optionService.createOption('lastSyncedPull', '0', false); optionService.createOption('lastSyncedPull', '0', false);
optionService.createOption('lastSyncedPush', '0', false); optionService.createOption('lastSyncedPush', '0', false);
let theme = 'dark'; // default based on the poll in https://github.com/zadam/trilium/issues/2516 optionService.createOption('theme', initOptions.getInitialTheme(), false);
if (utils.isElectron()) {
const {nativeTheme} = require('electron');
theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
}
optionService.createOption('theme', theme, false);
optionService.createOption('syncServerHost', opts.syncServerHost || '', false); optionService.createOption('syncServerHost', opts.syncServerHost || '', false);
optionService.createOption('syncServerTimeout', '120000', false); optionService.createOption('syncServerTimeout', '120000', false);

View File

@ -8,6 +8,7 @@ import appInfo = require('./app_info');
import utils = require('./utils'); import utils = require('./utils');
import becca = require('../becca/becca'); import becca = require('../becca/becca');
import { SetupStatusResponse, SetupSyncSeedResponse } from './api-interface'; import { SetupStatusResponse, SetupSyncSeedResponse } from './api-interface';
import { InitDbOptions } from '../types';
async function hasSyncServerSchemaAndSeed() { async function hasSyncServerSchemaAndSeed() {
const response = await requestToSyncServer<SetupStatusResponse>('GET', '/api/setup/status'); const response = await requestToSyncServer<SetupStatusResponse>('GET', '/api/setup/status');
@ -56,7 +57,7 @@ async function requestToSyncServer<T>(method: string, path: string, body?: strin
}), timeout) as T; }), timeout) as T;
} }
async function setupSyncFromSyncServer(syncServerHost: string, syncProxy: string, password: string) { async function setupSyncFromSyncServer(syncServerHost: string, syncProxy: string, password: string, initOptions: InitDbOptions) {
if (sqlInit.isDbInitialized()) { if (sqlInit.isDbInitialized()) {
return { return {
result: 'failure', result: 'failure',
@ -87,7 +88,7 @@ async function setupSyncFromSyncServer(syncServerHost: string, syncProxy: string
} }
} }
sqlInit.createDatabaseForSync(resp.options, syncServerHost, syncProxy); sqlInit.createDatabaseForSync(resp.options, initOptions, syncServerHost, syncProxy);
triggerSync(); triggerSync();

View File

@ -11,6 +11,8 @@ import migrationService = require('./migration');
import cls = require('./cls'); import cls = require('./cls');
import config = require('./config'); import config = require('./config');
import { OptionRow } from '../becca/entities/rows'; import { OptionRow } from '../becca/entities/rows';
import optionsInitService = require("./options_init");
import { InitDbOptions } from '../types';
const dbReady = utils.deferred<void>(); const dbReady = utils.deferred<void>();
@ -46,7 +48,7 @@ async function initDbConnection() {
dbReady.resolve(); dbReady.resolve();
} }
async function createInitialDatabase() { async function createInitialDatabase(initOptions: InitDbOptions) {
if (isDbInitialized()) { if (isDbInitialized()) {
throw new Error("DB is already initialized"); throw new Error("DB is already initialized");
} }
@ -84,10 +86,8 @@ async function createInitialDatabase() {
notePosition: 10 notePosition: 10
}).save(); }).save();
const optionsInitService = require('./options_init');
optionsInitService.initDocumentOptions(); optionsInitService.initDocumentOptions();
optionsInitService.initNotSyncedOptions(true, {}); optionsInitService.initNotSyncedOptions(true, {}, initOptions);
optionsInitService.initStartupOptions(); optionsInitService.initStartupOptions();
require('./encryption/password').resetPassword(); require('./encryption/password').resetPassword();
}); });
@ -120,7 +120,7 @@ async function createInitialDatabase() {
initDbConnection(); initDbConnection();
} }
function createDatabaseForSync(options: OptionRow[], syncServerHost = '', syncProxy = '') { function createDatabaseForSync(options: OptionRow[], initOptions: InitDbOptions, syncServerHost = '', syncProxy = '') {
log.info("Creating database for sync"); log.info("Creating database for sync");
if (isDbInitialized()) { if (isDbInitialized()) {
@ -132,7 +132,7 @@ function createDatabaseForSync(options: OptionRow[], syncServerHost = '', syncPr
sql.transactional(() => { sql.transactional(() => {
sql.executeScript(schema); sql.executeScript(schema);
require('./options_init').initNotSyncedOptions(false, { syncServerHost, syncProxy }); optionsInitService.initNotSyncedOptions(false, { syncServerHost, syncProxy }, initOptions);
// document options required for sync to kick off // document options required for sync to kick off
for (const opt of options) { for (const opt of options) {

23
server/src/types.ts Normal file
View File

@ -0,0 +1,23 @@
import { Response } from "express";
export type InitialThemeCallback = () => "dark" | "light";
export type SetupCompleteCallback = (res: Response) => void;
/**
* Options needed when the database is first initialized, such as methods to obtain default values.
*/
export interface InitDbOptions {
/**
* Called during first setup, in order to determine which theme to set for the user.
*/
getInitialTheme: InitialThemeCallback;
}
/**
* Handles differences between clients, for example allowing different behaviour when running from the web server versus the desktop application.
*/
export interface AppConfig extends InitDbOptions {
/** Callback to be invoked when first setup is complete. */
setupCompleteCallback: SetupCompleteCallback;
}

View File

@ -31,9 +31,9 @@ import port = require('./services/port');
import host = require('./services/host'); import host = require('./services/host');
import semver = require('semver'); import semver = require('semver');
import type { Express } from "express"; import type { Express } from "express";
import { RouteConfig } from './routes/types'; import { AppConfig } from './types';
function startTrilium(routeConfig: RouteConfig) { function startTrilium(appConfig: AppConfig) {
if (!semver.satisfies(process.version, ">=10.5.0")) { if (!semver.satisfies(process.version, ">=10.5.0")) {
console.error("Trilium only supports node.js 10.5 and later"); console.error("Trilium only supports node.js 10.5 and later");
process.exit(1); process.exit(1);
@ -41,7 +41,7 @@ function startTrilium(routeConfig: RouteConfig) {
log.info(JSON.stringify(appInfo, null, 2)); log.info(JSON.stringify(appInfo, null, 2));
const app = buildApp(routeConfig); const app = buildApp(appConfig);
const cpuInfos = require('os').cpus(); const cpuInfos = require('os').cpus();
if (cpuInfos && cpuInfos[0] !== undefined) { // https://github.com/zadam/trilium/pull/3957 if (cpuInfos && cpuInfos[0] !== undefined) { // https://github.com/zadam/trilium/pull/3957