Refactor imports, streamline database IPC handlers, and improve offline support

- Replace absolute import paths with relative paths for consistency across files.
- Transition database operations in Electron main process to modular handlers in `ipc/book.ipc.ts` using the `createHandler` pattern.
- Update database sync service to use `window.electron.invoke()` for improved reliability and structure.
- Refactor `AddNewBookForm` to handle both online and offline book creation seamlessly.
This commit is contained in:
natreex
2025-11-18 21:28:41 -05:00
parent d018e75be4
commit 004008cc13
13 changed files with 45 additions and 237 deletions

View File

@@ -27,7 +27,7 @@ import GuideTour, {GuideStep} from "@/components/GuideTour";
import {UserProps} from "@/lib/models/User"; import {UserProps} from "@/lib/models/User";
import {useTranslations} from "next-intl"; import {useTranslations} from "next-intl";
import {LangContext, LangContextProps} from "@/context/LangContext"; import {LangContext, LangContextProps} from "@/context/LangContext";
// TODO: Refactor to use window.electron.invoke() instead of OfflineDataService import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
interface MinMax { interface MinMax {
min: number; min: number;
@@ -39,6 +39,7 @@ export default function AddNewBookForm({setCloseForm}: { setCloseForm: Dispatch<
const {lang} = useContext<LangContextProps>(LangContext); const {lang} = useContext<LangContextProps>(LangContext);
const {session, setSession} = useContext(SessionContext); const {session, setSession} = useContext(SessionContext);
const {errorMessage} = useContext(AlertContext); const {errorMessage} = useContext(AlertContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const modalRef: React.RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null); const modalRef: React.RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null);
const [title, setTitle] = useState<string>(''); const [title, setTitle] = useState<string>('');
@@ -123,7 +124,6 @@ export default function AddNewBookForm({setCloseForm}: { setCloseForm: Dispatch<
} }
setIsAddingBook(true); setIsAddingBook(true);
try { try {
const offlineDataService = getOfflineDataService();
const bookData = { const bookData = {
title, title,
subTitle: subtitle, subTitle: subtitle,
@@ -134,26 +134,25 @@ export default function AddNewBookForm({setCloseForm}: { setCloseForm: Dispatch<
desiredWordCount: wordCount desiredWordCount: wordCount
}; };
const bookId: string = await offlineDataService.createBook( let bookId: string;
bookData, if (!isCurrentlyOffline()) {
session.user?.id || '', // Online - call API server
async () => { bookId = await System.authPostToServer<string>('book/add', {
// Only called if online title: title,
const id = await System.authPostToServer<string>('book/add', { subTitle: subtitle,
title: title, type: selectedBookType,
subTitle: subtitle, summary: summary,
type: selectedBookType, serie: 0,
summary: summary, publicationDate: publicationDate,
serie: 0, desiredWordCount: wordCount,
publicationDate: publicationDate, }, token, lang);
desiredWordCount: wordCount, if (!bookId) {
}, token, lang); throw new Error(t('addNewBookForm.error.addingBook'));
if (!id) {
throw new Error(t('addNewBookForm.error.addingBook'));
}
return id;
} }
); } else {
// Offline - call local database
bookId = await window.electron.invoke<string>('db:book:create', bookData);
}
const book: BookProps = { const book: BookProps = {
bookId: bookId, bookId: bookId,

View File

@@ -18,8 +18,7 @@ import fs from "fs";
import BookRepo from "../repositories/book.repository.js"; import BookRepo from "../repositories/book.repository.js";
import Chapter, {ActChapter, ChapterContentData, ChapterProps} from "./Chapter.js"; import Chapter, {ActChapter, ChapterContentData, ChapterProps} from "./Chapter.js";
import UserRepo from "../repositories/user.repository.js"; import UserRepo from "../repositories/user.repository.js";
import ChapterRepo from "@/electron/database/repositories/chapter.repository"; import ChapterRepo from "../repositories/chapter.repository.js";
import {mainStyle} from "@/electron/database/models/EpubStyle";
export interface BookProps{ export interface BookProps{
id:string; id:string;

View File

@@ -8,9 +8,9 @@ import ChapterRepo, {
CompanionContentQueryResult, CompanionContentQueryResult,
ChapterStoryQueryResult, ChapterStoryQueryResult,
ContentQueryResult ContentQueryResult
} from "@/electron/database/repositories/chapter.repository"; } from "../repositories/chapter.repository.js";
import System from "../System"; import System from "../System.js";
import {getUserEncryptionKey} from "../keyManager"; import {getUserEncryptionKey} from "../keyManager.js";
import { generateHTML } from "@tiptap/react"; import { generateHTML } from "@tiptap/react";
export interface ChapterContent { export interface ChapterContent {

View File

@@ -2,9 +2,9 @@ import CharacterRepo, {
AttributeResult, AttributeResult,
CharacterResult, CharacterResult,
CompleteCharacterResult CompleteCharacterResult
} from "@/electron/database/repositories/character.repository"; } from "../repositories/character.repository.js";
import System from "@/electron/database/System"; import System from "../System.js";
import {getUserEncryptionKey} from "../keyManager"; import {getUserEncryptionKey} from "../keyManager.js";
export type CharacterCategory = 'Main' | 'Secondary' | 'Recurring'; export type CharacterCategory = 'Main' | 'Secondary' | 'Recurring';

View File

@@ -2,9 +2,9 @@ import LocationRepo, {
LocationByTagResult, LocationByTagResult,
LocationElementQueryResult, LocationElementQueryResult,
LocationQueryResult LocationQueryResult
} from "@/electron/database/repositories/location.repository"; } from "../repositories/location.repository.js";
import System from "@/electron/database/System"; import System from "../System.js";
import {getUserEncryptionKey} from "../keyManager"; import {getUserEncryptionKey} from "../keyManager.js";
export interface SubElement { export interface SubElement {
id: string; id: string;

View File

@@ -1,7 +1,7 @@
import UserRepo, {UserAccountQuery, UserInfosQueryResponse} from "@/electron/database/repositories/user.repository"; import UserRepo, {UserAccountQuery, UserInfosQueryResponse} from "../repositories/user.repository.js";
import System from "@/electron/database/System"; import System from "../System.js";
import Book, {BookProps} from "@/electron/database/models/Book"; import Book, {BookProps} from "./Book.js";
import {getUserEncryptionKey} from "@/electron/database/keyManager"; import {getUserEncryptionKey} from "../keyManager.js";
interface UserAccount{ interface UserAccount{
firstName:string; firstName:string;

View File

@@ -1,5 +1,5 @@
import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm'; import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "@/electron/database/System"; import System from "../System.js";
export interface BookQuery extends Record<string, SQLiteValue> { export interface BookQuery extends Record<string, SQLiteValue> {
book_id: string; book_id: string;

View File

@@ -1,5 +1,5 @@
import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm'; import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "../System"; import System from "../System.js";
export interface ChapterContentQueryResult extends Record<string, SQLiteValue>{ export interface ChapterContentQueryResult extends Record<string, SQLiteValue>{
chapter_id: string; chapter_id: string;

View File

@@ -1,5 +1,5 @@
import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm'; import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "../System"; import System from "../System.js";
export interface CharacterResult extends Record<string, SQLiteValue> { export interface CharacterResult extends Record<string, SQLiteValue> {
character_id: string; character_id: string;

View File

@@ -1,5 +1,5 @@
import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm'; import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "../System"; import System from "../System.js";
export interface LocationQueryResult extends Record<string, SQLiteValue> { export interface LocationQueryResult extends Record<string, SQLiteValue> {
loc_id: string; loc_id: string;

View File

@@ -1,5 +1,5 @@
import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm'; import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "../System"; import System from "../System.js";
export interface UserInfosQueryResponse extends Record<string, SQLiteValue> { export interface UserInfosQueryResponse extends Record<string, SQLiteValue> {
first_name: string; first_name: string;

View File

@@ -222,192 +222,8 @@ ipcMain.handle('db-initialize', (_event, userId: string, encryptionKey: string)
} }
}); });
/** // NOTE: All database IPC handlers have been moved to ./ipc/book.ipc.ts
* Get all books // and use the new createHandler() pattern with auto userId/lang injection
*/
ipcMain.handle('db-get-books', () => {
try {
const db = getDatabaseService();
const books = db.getBooks();
return { success: true, data: books };
} catch (error) {
console.error('Failed to get books:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Get single book with all data
*/
ipcMain.handle('db-get-book', (_event, bookId: string) => {
try {
const db = getDatabaseService();
const book = db.getBook(bookId);
return { success: true, data: book };
} catch (error) {
console.error('Failed to get book:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Save book
*/
ipcMain.handle('db-save-book', (_event, book: any, authorId?: string) => {
try {
const db = getDatabaseService();
db.saveBook(book, authorId);
return { success: true };
} catch (error) {
console.error('Failed to save book:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Delete book
*/
ipcMain.handle('db-delete-book', (_event, bookId: string) => {
try {
const db = getDatabaseService();
db.deleteBook(bookId);
return { success: true };
} catch (error) {
console.error('Failed to delete book:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Save chapter
*/
ipcMain.handle('db-save-chapter', (_event, chapter: any, bookId: string, contentId?: string) => {
try {
const db = getDatabaseService();
db.saveChapter(chapter, bookId, contentId);
return { success: true };
} catch (error) {
console.error('Failed to save chapter:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Get characters for a book
*/
ipcMain.handle('db-get-characters', (_event, bookId: string) => {
try {
const db = getDatabaseService();
const characters = db.getCharacters(bookId);
return { success: true, data: characters };
} catch (error) {
console.error('Failed to get characters:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Save character
*/
ipcMain.handle('db-save-character', (_event, character: any, bookId: string) => {
try {
const db = getDatabaseService();
db.saveCharacter(character, bookId);
return { success: true };
} catch (error) {
console.error('Failed to save character:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Get AI conversations for a book
*/
ipcMain.handle('db-get-conversations', (_event, bookId: string) => {
try {
const db = getDatabaseService();
const conversations = db.getConversations(bookId);
return { success: true, data: conversations };
} catch (error) {
console.error('Failed to get conversations:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Save AI conversation
*/
ipcMain.handle('db-save-conversation', (_event, conversation: any, bookId: string) => {
try {
const db = getDatabaseService();
db.saveConversation(conversation, bookId);
return { success: true };
} catch (error) {
console.error('Failed to save conversation:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Get sync status
*/
ipcMain.handle('db-get-sync-status', () => {
try {
const db = getDatabaseService();
const status = db.getSyncStatus();
return { success: true, data: status };
} catch (error) {
console.error('Failed to get sync status:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
/**
* Get pending changes
*/
ipcMain.handle('db-get-pending-changes', (_event, limit: number = 100) => {
try {
const db = getDatabaseService();
const changes = db.getPendingChanges(limit);
return { success: true, data: changes };
} catch (error) {
console.error('Failed to get pending changes:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
app.whenReady().then(() => { app.whenReady().then(() => {
console.log('App ready, isDev:', isDev); console.log('App ready, isDev:', isDev);

View File

@@ -21,11 +21,8 @@ export async function getSyncStatus(): Promise<SyncProgress> {
} }
try { try {
const result = await window.electron.dbGetSyncStatus(); const result = await window.electron.invoke<SyncProgress>('db:sync:status');
if (!result.success) { return result;
throw new Error(result.error);
}
return result.data;
} catch (error) { } catch (error) {
console.error('Failed to get sync status:', error); console.error('Failed to get sync status:', error);
return { return {
@@ -46,11 +43,8 @@ export async function getPendingChanges(limit: number = 100) {
} }
try { try {
const result = await window.electron.dbGetPendingChanges(limit); const result = await window.electron.invoke<any[]>('db:sync:pending-changes', limit);
if (!result.success) { return result || [];
throw new Error(result.error);
}
return result.data || [];
} catch (error) { } catch (error) {
console.error('Failed to get pending changes:', error); console.error('Failed to get pending changes:', error);
return []; return [];