diff --git a/electron/src/electron.ts b/electron/src/electron.ts index 3b005c0b2..69b0b95b4 100644 --- a/electron/src/electron.ts +++ b/electron/src/electron.ts @@ -81,6 +81,10 @@ const app = startTrilium({ setupCompleteCallback: () => { windowService.createMainWindow(electron.app); windowService.closeSetupWindow(); + }, + + getInitialTheme() { + return electron.nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; } }); electronRouting(app); \ No newline at end of file diff --git a/server/src/app.ts b/server/src/app.ts index 0300d3e06..03e9eb338 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -6,9 +6,9 @@ import helmet = require('helmet'); import compression = require('compression'); import sessionParser = require('./routes/session_parser'); 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('./becca/becca_loader'); @@ -40,7 +40,7 @@ function buildApp(routeConfig: RouteConfig) { app.use(favicon(`${__dirname}/../../common/images/app-icons/win/icon.ico`)); require('./routes/assets').register(app); - require('./routes/routes').register(app, routeConfig); + require('./routes/routes').register(app, appConfig); require('./routes/custom').register(app); require('./routes/error_handlers').register(app); diff --git a/server/src/index.ts b/server/src/index.ts index f3d30879c..032638bf0 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -3,5 +3,10 @@ import startTrilium = require("./www"); startTrilium({ setupCompleteCallback: (res) => { res.redirect('.'); + }, + + getInitialTheme() { + // default based on the poll in https://github.com/zadam/trilium/issues/2516 + return "dark"; } }); \ No newline at end of file diff --git a/server/src/routes/api/setup.ts b/server/src/routes/api/setup.ts index 9e63a2331..ff5b5b842 100644 --- a/server/src/routes/api/setup.ts +++ b/server/src/routes/api/setup.ts @@ -5,56 +5,61 @@ import setupService = require('../../services/setup'); import log = require('../../services/log'); import appInfo = require('../../services/app_info'); import { Request } from 'express'; +import { InitDbOptions } from '../../types'; -function getStatus() { - return { - isInitialized: sqlInit.isDbInitialized(), - 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 - }] +function buildRoutes(initOptions: InitDbOptions) { + function getStatus() { + return { + isInitialized: sqlInit.isDbInitialized(), + schemaExists: sqlInit.schemaExists(), + syncVersion: appInfo.syncVersion + }; } - - log.info("Saved sync seed."); - - sqlInit.createDatabaseForSync(options); -} - -function getSyncSeed() { - log.info("Serving sync seed."); - + + async function setupNewDocument() { + await sqlInit.createInitialDatabase(initOptions); + } + + function setupSyncFromServer(req: Request) { + const { syncServerHost, syncProxy, password } = req.body; + + return setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, password, initOptions); + } + + 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 { - options: setupService.getSyncSeedOptions(), - syncVersion: appInfo.syncVersion + getStatus, + setupNewDocument, + setupSyncFromServer, + getSyncSeed, + saveSyncSeed }; } -export = { - getStatus, - setupNewDocument, - setupSyncFromServer, - getSyncSeed, - saveSyncSeed -}; +export = buildRoutes; \ No newline at end of file diff --git a/server/src/routes/routes.ts b/server/src/routes/routes.ts index 8e9577799..339b5dd38 100644 --- a/server/src/routes/routes.ts +++ b/server/src/routes/routes.ts @@ -38,7 +38,7 @@ import recentNotesRoute = require('./api/recent_notes'); import appInfoRoute = require('./api/app_info'); import exportRoute = require('./api/export'); import importRoute = require('./api/import'); -import setupApiRoute = require('./api/setup'); +import buildSetupRoute = require('./api/setup'); import sqlRoute = require('./api/sql'); import databaseRoute = require('./api/database'); import imageRoute = require('./api/image'); @@ -71,7 +71,7 @@ import etapiSpecialNoteRoutes = require('../etapi/special_notes'); import etapiSpecRoute = require('../etapi/spec'); import etapiBackupRoute = require('../etapi/backup'); import { AppRequest, AppRequestHandler } from './route-interface'; -import { RouteConfig } from './types'; +import { AppConfig } from '../types'; const csrfMiddleware = csurf({ 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, '/login', [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage); 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, '/logout', [csrfMiddleware, auth.checkAuth], loginRoute.logout); 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(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); // 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(PST, '/api/setup/new-document', [auth.checkAppNotInitialized], setupApiRoute.setupNewDocument, apiResultHandler, false); route(PST, '/api/setup/sync-from-server', [auth.checkAppNotInitialized], setupApiRoute.setupSyncFromServer, apiResultHandler, false); diff --git a/server/src/routes/setup.ts b/server/src/routes/setup.ts index 66b2c975a..e3987f23c 100644 --- a/server/src/routes/setup.ts +++ b/server/src/routes/setup.ts @@ -5,7 +5,7 @@ import setupService = require('../services/setup'); import assetPath = require('../services/asset_path'); import appPath = require('../services/app_path'); import { Request, Response } from 'express'; -import { SetupCompleteCallback } from './types'; +import { SetupCompleteCallback } from '../types'; function buildSetupRoute(setupCompleteCallback: SetupCompleteCallback) { return (req: Request, res: Response) => { diff --git a/server/src/routes/types.ts b/server/src/routes/types.ts deleted file mode 100644 index 898404249..000000000 --- a/server/src/routes/types.ts +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/server/src/services/options_init.ts b/server/src/services/options_init.ts index e2ba1520a..8e9654f3a 100644 --- a/server/src/services/options_init.ts +++ b/server/src/services/options_init.ts @@ -5,6 +5,7 @@ import log = require('./log'); import dateUtils = require('./date_utils'); import keyboardActions = require('./keyboard_actions'); import { KeyboardShortcutWithRequiredActionName } from './keyboard_actions_interface'; +import { InitDbOptions } from '../types'; function initDocumentOptions() { optionService.createOption('documentId', utils.randomSecureToken(16), false); @@ -16,7 +17,7 @@ interface NotSyncedOpts { syncProxy?: string; } -function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}) { +function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}, initOptions: InitDbOptions) { optionService.createOption('openNoteContexts', JSON.stringify([ { notePath: 'root', @@ -32,17 +33,9 @@ function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}) { optionService.createOption('initialized', initialized ? 'true' : 'false', 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 - - if (utils.isElectron()) { - const {nativeTheme} = require('electron'); - - theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; - } - - optionService.createOption('theme', theme, false); + optionService.createOption('theme', initOptions.getInitialTheme(), false); optionService.createOption('syncServerHost', opts.syncServerHost || '', false); optionService.createOption('syncServerTimeout', '120000', false); diff --git a/server/src/services/setup.ts b/server/src/services/setup.ts index 08b1ee806..bf7384892 100644 --- a/server/src/services/setup.ts +++ b/server/src/services/setup.ts @@ -8,6 +8,7 @@ import appInfo = require('./app_info'); import utils = require('./utils'); import becca = require('../becca/becca'); import { SetupStatusResponse, SetupSyncSeedResponse } from './api-interface'; +import { InitDbOptions } from '../types'; async function hasSyncServerSchemaAndSeed() { const response = await requestToSyncServer('GET', '/api/setup/status'); @@ -56,7 +57,7 @@ async function requestToSyncServer(method: string, path: string, body?: strin }), 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()) { return { 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(); diff --git a/server/src/services/sql_init.ts b/server/src/services/sql_init.ts index 5c9937daa..89c74fc90 100644 --- a/server/src/services/sql_init.ts +++ b/server/src/services/sql_init.ts @@ -11,6 +11,8 @@ import migrationService = require('./migration'); import cls = require('./cls'); import config = require('./config'); import { OptionRow } from '../becca/entities/rows'; +import optionsInitService = require("./options_init"); +import { InitDbOptions } from '../types'; const dbReady = utils.deferred(); @@ -46,7 +48,7 @@ async function initDbConnection() { dbReady.resolve(); } -async function createInitialDatabase() { +async function createInitialDatabase(initOptions: InitDbOptions) { if (isDbInitialized()) { throw new Error("DB is already initialized"); } @@ -84,10 +86,8 @@ async function createInitialDatabase() { notePosition: 10 }).save(); - const optionsInitService = require('./options_init'); - optionsInitService.initDocumentOptions(); - optionsInitService.initNotSyncedOptions(true, {}); + optionsInitService.initNotSyncedOptions(true, {}, initOptions); optionsInitService.initStartupOptions(); require('./encryption/password').resetPassword(); }); @@ -120,7 +120,7 @@ async function createInitialDatabase() { initDbConnection(); } -function createDatabaseForSync(options: OptionRow[], syncServerHost = '', syncProxy = '') { +function createDatabaseForSync(options: OptionRow[], initOptions: InitDbOptions, syncServerHost = '', syncProxy = '') { log.info("Creating database for sync"); if (isDbInitialized()) { @@ -132,7 +132,7 @@ function createDatabaseForSync(options: OptionRow[], syncServerHost = '', syncPr sql.transactional(() => { sql.executeScript(schema); - require('./options_init').initNotSyncedOptions(false, { syncServerHost, syncProxy }); + optionsInitService.initNotSyncedOptions(false, { syncServerHost, syncProxy }, initOptions); // document options required for sync to kick off for (const opt of options) { diff --git a/server/src/types.ts b/server/src/types.ts new file mode 100644 index 000000000..5d7eb9d54 --- /dev/null +++ b/server/src/types.ts @@ -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; +} diff --git a/server/src/www.ts b/server/src/www.ts index 8a3e266fa..3956b2a10 100644 --- a/server/src/www.ts +++ b/server/src/www.ts @@ -31,9 +31,9 @@ import port = require('./services/port'); import host = require('./services/host'); import semver = require('semver'); 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")) { console.error("Trilium only supports node.js 10.5 and later"); process.exit(1); @@ -41,7 +41,7 @@ function startTrilium(routeConfig: RouteConfig) { log.info(JSON.stringify(appInfo, null, 2)); - const app = buildApp(routeConfig); + const app = buildApp(appConfig); const cpuInfos = require('os').cpus(); if (cpuInfos && cpuInfos[0] !== undefined) { // https://github.com/zadam/trilium/pull/3957