mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	chore(monorepo/server): move server-side source code
This commit is contained in:
		
							
								
								
									
										234
									
								
								apps/server/src/services/sql_init.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								apps/server/src/services/sql_init.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,234 @@ | ||||
| import log from "./log.js"; | ||||
| import fs from "fs"; | ||||
| import resourceDir from "./resource_dir.js"; | ||||
| import sql from "./sql.js"; | ||||
| import { isElectron, deferred } from "./utils.js"; | ||||
| import optionService from "./options.js"; | ||||
| import port from "./port.js"; | ||||
| import BOption from "../becca/entities/boption.js"; | ||||
| import TaskContext from "./task_context.js"; | ||||
| import migrationService from "./migration.js"; | ||||
| import cls from "./cls.js"; | ||||
| import config from "./config.js"; | ||||
| import type { OptionRow } from "../becca/entities/rows.js"; | ||||
| import BNote from "../becca/entities/bnote.js"; | ||||
| import BBranch from "../becca/entities/bbranch.js"; | ||||
| import zipImportService from "./import/zip.js"; | ||||
| import becca_loader from "../becca/becca_loader.js"; | ||||
| import password from "./encryption/password.js"; | ||||
| import backup from "./backup.js"; | ||||
| import eventService from "./events.js"; | ||||
|  | ||||
| const dbReady = deferred<void>(); | ||||
|  | ||||
| function schemaExists() { | ||||
|     return !!sql.getValue(/*sql*/`SELECT name FROM sqlite_master | ||||
|                                 WHERE type = 'table' AND name = 'options'`); | ||||
| } | ||||
|  | ||||
| function isDbInitialized() { | ||||
|     if (!schemaExists()) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     const initialized = sql.getValue("SELECT value FROM options WHERE name = 'initialized'"); | ||||
|  | ||||
|     return initialized === "true"; | ||||
| } | ||||
|  | ||||
| async function initDbConnection() { | ||||
|     if (!isDbInitialized()) { | ||||
|         log.info(`DB not initialized, please visit setup page` + (isElectron ? "" : ` - http://[your-server-host]:${port} to see instructions on how to initialize Trilium.`)); | ||||
|  | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     await migrationService.migrateIfNecessary(); | ||||
|  | ||||
|     sql.execute('CREATE TEMP TABLE "param_list" (`paramId` TEXT NOT NULL PRIMARY KEY)'); | ||||
|  | ||||
|     sql.execute(` | ||||
|     CREATE TABLE IF NOT EXISTS "user_data" | ||||
|     ( | ||||
|         tmpID INT, | ||||
|         username TEXT, | ||||
|         email TEXT, | ||||
|         userIDEncryptedDataKey TEXT, | ||||
|         userIDVerificationHash TEXT, | ||||
|         salt TEXT, | ||||
|         derivedKey TEXT, | ||||
|         isSetup TEXT DEFAULT "false", | ||||
|         UNIQUE (tmpID), | ||||
|         PRIMARY KEY (tmpID) | ||||
|     );`) | ||||
|  | ||||
|     dbReady.resolve(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Applies the database schema, creating the necessary tables and importing the demo content. | ||||
|  * | ||||
|  * @param skipDemoDb if set to `true`, then the demo database will not be imported, resulting in an empty root note. | ||||
|  * @throws {Error} if the database is already initialized. | ||||
|  */ | ||||
| async function createInitialDatabase(skipDemoDb?: boolean) { | ||||
|     if (isDbInitialized()) { | ||||
|         throw new Error("DB is already initialized"); | ||||
|     } | ||||
|  | ||||
|     const schema = fs.readFileSync(`${resourceDir.DB_INIT_DIR}/schema.sql`, "utf-8"); | ||||
|     const demoFile = (!skipDemoDb ? fs.readFileSync(`${resourceDir.DB_INIT_DIR}/demo.zip`) : null); | ||||
|  | ||||
|     let rootNote!: BNote; | ||||
|  | ||||
|     // We have to import async since options init requires keyboard actions which require translations. | ||||
|     const optionsInitService = (await import("./options_init.js")).default; | ||||
|  | ||||
|     sql.transactional(() => { | ||||
|         log.info("Creating database schema ..."); | ||||
|  | ||||
|         sql.executeScript(schema); | ||||
|  | ||||
|         becca_loader.load(); | ||||
|  | ||||
|         log.info("Creating root note ..."); | ||||
|  | ||||
|         rootNote = new BNote({ | ||||
|             noteId: "root", | ||||
|             title: "root", | ||||
|             type: "text", | ||||
|             mime: "text/html" | ||||
|         }).save(); | ||||
|  | ||||
|         rootNote.setContent(""); | ||||
|  | ||||
|         new BBranch({ | ||||
|             noteId: "root", | ||||
|             parentNoteId: "none", | ||||
|             isExpanded: true, | ||||
|             notePosition: 10 | ||||
|         }).save(); | ||||
|  | ||||
|         optionsInitService.initDocumentOptions(); | ||||
|         optionsInitService.initNotSyncedOptions(true, {}); | ||||
|         optionsInitService.initStartupOptions(); | ||||
|         password.resetPassword(); | ||||
|     }); | ||||
|  | ||||
|     log.info("Importing demo content ..."); | ||||
|  | ||||
|     const dummyTaskContext = new TaskContext("no-progress-reporting", "import", false); | ||||
|  | ||||
|     if (demoFile) { | ||||
|         await zipImportService.importZip(dummyTaskContext, demoFile, rootNote); | ||||
|     } | ||||
|  | ||||
|     sql.transactional(() => { | ||||
|         // this needs to happen after ZIP import, | ||||
|         // the previous solution was to move option initialization here, but then the important parts of initialization | ||||
|         // are not all in one transaction (because ZIP import is async and thus not transactional) | ||||
|  | ||||
|         const startNoteId = sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition"); | ||||
|  | ||||
|         optionService.setOption( | ||||
|             "openNoteContexts", | ||||
|             JSON.stringify([ | ||||
|                 { | ||||
|                     notePath: startNoteId, | ||||
|                     active: true | ||||
|                 } | ||||
|             ]) | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     log.info("Schema and initial content generated."); | ||||
|  | ||||
|     initDbConnection(); | ||||
| } | ||||
|  | ||||
| async function createDatabaseForSync(options: OptionRow[], syncServerHost = "", syncProxy = "") { | ||||
|     log.info("Creating database for sync"); | ||||
|  | ||||
|     if (isDbInitialized()) { | ||||
|         throw new Error("DB is already initialized"); | ||||
|     } | ||||
|  | ||||
|     const schema = fs.readFileSync(`${resourceDir.DB_INIT_DIR}/schema.sql`, "utf8"); | ||||
|  | ||||
|     // We have to import async since options init requires keyboard actions which require translations. | ||||
|     const optionsInitService = (await import("./options_init.js")).default; | ||||
|  | ||||
|     sql.transactional(() => { | ||||
|         sql.executeScript(schema); | ||||
|  | ||||
|         optionsInitService.initNotSyncedOptions(false, { syncServerHost, syncProxy }); | ||||
|  | ||||
|         // document options required for sync to kick off | ||||
|         for (const opt of options) { | ||||
|             new BOption(opt).save(); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     log.info("Schema and not synced options generated."); | ||||
| } | ||||
|  | ||||
| function setDbAsInitialized() { | ||||
|     if (!isDbInitialized()) { | ||||
|         optionService.setOption("initialized", "true"); | ||||
|  | ||||
|         initDbConnection(); | ||||
|  | ||||
|         // Emit an event to notify that the database is now initialized | ||||
|         eventService.emit(eventService.DB_INITIALIZED); | ||||
|  | ||||
|         log.info("Database initialization completed, emitted DB_INITIALIZED event"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function optimize() { | ||||
|     log.info("Optimizing database"); | ||||
|     const start = Date.now(); | ||||
|  | ||||
|     sql.execute("PRAGMA optimize"); | ||||
|  | ||||
|     log.info(`Optimization finished in ${Date.now() - start}ms.`); | ||||
| } | ||||
|  | ||||
| function getDbSize() { | ||||
|     return sql.getValue<number>("SELECT page_count * page_size / 1000 as size FROM pragma_page_count(), pragma_page_size()"); | ||||
| } | ||||
|  | ||||
| function initializeDb() { | ||||
|     cls.init(initDbConnection); | ||||
|  | ||||
|     log.info(`DB size: ${getDbSize()} KB`); | ||||
|  | ||||
|     dbReady.then(() => { | ||||
|         if (config.General && config.General.noBackup === true) { | ||||
|             log.info("Disabling scheduled backups."); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         setInterval(() => backup.regularBackup(), 4 * 60 * 60 * 1000); | ||||
|  | ||||
|         // kickoff first backup soon after start up | ||||
|         setTimeout(() => backup.regularBackup(), 5 * 60 * 1000); | ||||
|  | ||||
|         // optimize is usually inexpensive no-op, so running it semi-frequently is not a big deal | ||||
|         setTimeout(() => optimize(), 60 * 60 * 1000); | ||||
|  | ||||
|         setInterval(() => optimize(), 10 * 60 * 60 * 1000); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     dbReady, | ||||
|     schemaExists, | ||||
|     isDbInitialized, | ||||
|     createInitialDatabase, | ||||
|     createDatabaseForSync, | ||||
|     setDbAsInitialized, | ||||
|     getDbSize, | ||||
|     initializeDb | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user