Integrate session management, multilingual IPC handlers, and Book database operations

- Add `LocalSystem` with handlers for session retrieval (`userId`, `lang`) and error handling.
- Extend IPC handlers to support multilingual operations (`fr`, `en`) and session data injection
This commit is contained in:
natreex
2025-11-18 19:51:17 -05:00
parent 5a891a72ff
commit d46aecc80d
5 changed files with 874 additions and 2 deletions

View File

@@ -0,0 +1,282 @@
import type { IpcMainInvokeEvent } from 'electron';
import Store from 'electron-store';
// Electron store instance for session management
const store = new Store({
encryptionKey: 'eritors-scribe-secure-key'
});
// ============================================================
// SESSION MANAGEMENT - Retrieve userId and lang from store
// ============================================================
/**
* Get userId from electron-store
* Set during login via 'login-success' event
*/
function getUserIdFromSession(): string | null {
return store.get('userId', null) as string | null;
}
/**
* Get lang from electron-store
* Set via 'set-lang' handler, defaults to 'fr'
*/
function getLangFromSession(): 'fr' | 'en' {
return store.get('userLang', 'fr') as 'fr' | 'en';
}
// ============================================================
// LEGACY HANDLERS - Manual userId injection, lang must be passed
// Keep these for backward compatibility
// Updated to support Promises
// ============================================================
export function createDbHandler<TReturn>(
handler: (userId: string) => TReturn | Promise<TReturn>
): (event: IpcMainInvokeEvent) => Promise<TReturn> {
return async function(event: IpcMainInvokeEvent): Promise<TReturn> {
const userId = getUserIdFromSession();
if (!userId) {
throw new Error('User not authenticated');
}
try {
return await handler(userId);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[DB] ${error.message}`);
throw error;
}
throw new Error('An unknown error occurred.');
}
};
}
export function createDbHandler1<T1, TReturn>(
handler: (userId: string, arg1: T1) => TReturn | Promise<TReturn>
): (event: IpcMainInvokeEvent, arg1: T1) => Promise<TReturn> {
return async function(event: IpcMainInvokeEvent, arg1: T1): Promise<TReturn> {
const userId = getUserIdFromSession();
if (!userId) {
throw new Error('User not authenticated');
}
try {
return await handler(userId, arg1);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[DB] ${error.message}`);
throw error;
}
throw new Error('An unknown error occurred.');
}
};
}
export function createDbHandler2<T1, T2, TReturn>(
handler: (userId: string, arg1: T1, arg2: T2) => TReturn | Promise<TReturn>
): (event: IpcMainInvokeEvent, arg1: T1, arg2: T2) => Promise<TReturn> {
return async function(event: IpcMainInvokeEvent, arg1: T1, arg2: T2): Promise<TReturn> {
const userId = getUserIdFromSession();
if (!userId) {
throw new Error('User not authenticated');
}
try {
return await handler(userId, arg1, arg2);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[DB] ${error.message}`);
throw error;
}
throw new Error('An unknown error occurred.');
}
};
}
export function createDbHandler3<T1, T2, T3, TReturn>(
handler: (userId: string, arg1: T1, arg2: T2, arg3: T3) => TReturn | Promise<TReturn>
): (event: IpcMainInvokeEvent, arg1: T1, arg2: T2, arg3: T3) => Promise<TReturn> {
return async function(event: IpcMainInvokeEvent, arg1: T1, arg2: T2, arg3: T3): Promise<TReturn> {
const userId = getUserIdFromSession();
if (!userId) {
throw new Error('User not authenticated');
}
try {
return await handler(userId, arg1, arg2, arg3);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[DB] ${error.message}`);
throw error;
}
throw new Error('An unknown error occurred.');
}
};
}
// ============================================================
// AUTO HANDLERS - Automatically inject userId AND lang
// Use these for new handlers - no need to pass lang from frontend
// ============================================================
/**
* Auto-handler with 0 parameters
* Automatically injects: userId, lang
*
* @example
* ipcMain.handle('db:user:get', createAutoHandler<UserProps>(
* function(userId: string, lang: 'fr' | 'en') {
* return User.getUser(userId, lang);
* }
* ));
*
* // Frontend call (no params needed):
* const user = await window.electron.invoke('db:user:get');
*/
export function createAutoHandler<TReturn>(
handler: (userId: string, lang: 'fr' | 'en') => TReturn | Promise<TReturn>
): (event: IpcMainInvokeEvent) => Promise<TReturn> {
return async function(event: IpcMainInvokeEvent): Promise<TReturn> {
const userId = getUserIdFromSession();
const lang = getLangFromSession();
if (!userId) {
throw new Error('User not authenticated');
}
try {
return await handler(userId, lang);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[DB] ${error.message}`);
throw error;
}
throw new Error('An unknown error occurred.');
}
};
}
/**
* Auto-handler with 1 parameter
* Automatically injects: userId, lang
*
* @example
* ipcMain.handle('db:book:get', createAutoHandler1<string, BookProps>(
* function(userId: string, bookId: string, lang: 'fr' | 'en') {
* return Book.getBook(bookId, userId, lang);
* }
* ));
*
* // Frontend call (only bookId needed):
* const book = await window.electron.invoke('db:book:get', bookId);
*/
export function createAutoHandler1<T1, TReturn>(
handler: (userId: string, arg1: T1, lang: 'fr' | 'en') => TReturn | Promise<TReturn>
): (event: IpcMainInvokeEvent, arg1: T1) => Promise<TReturn> {
return async function(event: IpcMainInvokeEvent, arg1: T1): Promise<TReturn> {
const userId = getUserIdFromSession();
const lang = getLangFromSession();
if (!userId) {
throw new Error('User not authenticated');
}
try {
return await handler(userId, arg1, lang);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[DB] ${error.message}`);
throw error;
}
throw new Error('An unknown error occurred.');
}
};
}
/**
* Auto-handler with 2 parameters
* Automatically injects: userId, lang
*
* @example
* ipcMain.handle('db:book:create', createAutoHandler1<CreateBookData, string>(
* function(userId: string, data: CreateBookData, lang: 'fr' | 'en') {
* return Book.addBook(null, userId, data.title, ..., lang);
* }
* ));
*
* // Frontend call (only data needed):
* const bookId = await window.electron.invoke('db:book:create', bookData);
*/
export function createAutoHandler2<T1, T2, TReturn>(
handler: (userId: string, arg1: T1, arg2: T2, lang: 'fr' | 'en') => TReturn | Promise<TReturn>
): (event: IpcMainInvokeEvent, arg1: T1, arg2: T2) => Promise<TReturn> {
return async function(event: IpcMainInvokeEvent, arg1: T1, arg2: T2): Promise<TReturn> {
const userId = getUserIdFromSession();
const lang = getLangFromSession();
if (!userId) {
throw new Error('User not authenticated');
}
try {
return await handler(userId, arg1, arg2, lang);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[DB] ${error.message}`);
throw error;
}
throw new Error('An unknown error occurred.');
}
};
}
/**
* Auto-handler with 3 parameters
* Automatically injects: userId, lang
*
* @example
* ipcMain.handle('db:book:cover:update', createAutoHandler2<string, string, boolean>(
* function(userId: string, bookId: string, coverImageName: string, lang: 'fr' | 'en') {
* return Book.updateBookCover(userId, bookId, coverImageName, lang);
* }
* ));
*
* // Frontend call (bookId and coverImageName needed):
* const success = await window.electron.invoke('db:book:cover:update', bookId, coverImageName);
*/
export function createAutoHandler3<T1, T2, T3, TReturn>(
handler: (userId: string, arg1: T1, arg2: T2, arg3: T3, lang: 'fr' | 'en') => TReturn | Promise<TReturn>
): (event: IpcMainInvokeEvent, arg1: T1, arg2: T2, arg3: T3) => Promise<TReturn> {
return async function(event: IpcMainInvokeEvent, arg1: T1, arg2: T2, arg3: T3): Promise<TReturn> {
const userId = getUserIdFromSession();
const lang = getLangFromSession();
if (!userId) {
throw new Error('User not authenticated');
}
try {
return await handler(userId, arg1, arg2, arg3, lang);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[DB] ${error.message}`);
throw error;
}
throw new Error('An unknown error occurred.');
}
};
}
export function createUniqueId(): string {
return crypto.randomUUID();
}
export function getCurrentDate(): string {
return new Date().toISOString();
}

View File

@@ -0,0 +1,66 @@
import { getDatabaseService } from './database.service.js';
import { encryptDataWithUserKey, decryptDataWithUserKey, hashElement } from './encryption.js';
import type { Database } from 'node-sqlite3-wasm';
import crypto from 'crypto';
export default class System {
public static getDb(): Database {
const db: Database | null = getDatabaseService().getDb();
if (!db) {
throw new Error('Database not initialized');
}
return db;
}
public static encryptDataWithUserKey(data: string, userKey: string): string {
return encryptDataWithUserKey(data, userKey);
}
public static decryptDataWithUserKey(encryptedData: string, userKey: string): string {
return decryptDataWithUserKey(encryptedData, userKey);
}
public static createUniqueId(): string {
return crypto.randomUUID();
}
static htmlToText(htmlNode: string): string {
let text: string = htmlNode
.replace(/<\/?p[^>]*>/gi, '\n')
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/?(span|h[1-6])[^>]*>/gi, '');
text = text
.replace(/&apos;/g, "'")
.replace(/&quot;/g, '"')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&#39;/g, "'");
text = text.replace(/\r?\n\s*\n/g, '\n');
text = text.replace(/[ \t]+/g, ' ');
return text.trim();
}
public static getCurrentDate(): string {
return new Date().toISOString();
}
static dateToMySqlDate(isoDateString: string): string {
const dateObject: Date = new Date(isoDateString);
function padWithZeroes(value: number): string {
return value.toString().padStart(2, '0');
}
const year: number = dateObject.getFullYear();
const month: string = padWithZeroes(dateObject.getMonth() + 1);
const day: string = padWithZeroes(dateObject.getDate());
return `${year}-${month}-${day}`;
}
public static hashElement(element: string): string {
return hashElement(element);
}
}