import System from '@/lib/models/System'; import { BookProps, BookListProps } from '@/lib/models/Book'; import { ChapterProps } from '@/lib/models/Chapter'; import { CharacterProps } from '@/lib/models/Character'; import { Conversation } from '@/lib/models/QuillSense'; /** * DataService - Smart routing layer between server API and local database * Automatically routes requests based on offline/online status */ export class DataService { private static isOffline: boolean = false; private static accessToken: string | null = null; /** * Set offline mode status */ static setOfflineMode(offline: boolean): void { this.isOffline = offline; } /** * Set access token for API requests */ static setAccessToken(token: string | null): void { this.accessToken = token; } /** * Check if currently offline */ static isCurrentlyOffline(): boolean { return this.isOffline; } // ========== BOOK OPERATIONS ========== /** * Get all books */ static async getBooks(): Promise { if (this.isOffline) { // Use local database if (typeof window === 'undefined' || !(window as any).electron) { throw new Error('Electron API not available'); } const result = await (window as any).electron.dbGetBooks(); if (!result.success) { throw new Error(result.error || 'Failed to get books from local DB'); } return result.data || []; } else { // Use server API if (!this.accessToken) { throw new Error('No access token available'); } const response = await System.authGetQueryToServer( 'books', this.accessToken ); return response.data || []; } } /** * Get a single book with all data */ static async getBook(bookId: string): Promise { if (this.isOffline) { // Use local database if (typeof window === 'undefined' || !(window as any).electron) { throw new Error('Electron API not available'); } const result = await (window as any).electron.dbGetBook(bookId); if (!result.success) { throw new Error(result.error || 'Failed to get book from local DB'); } return result.data || null; } else { // Use server API if (!this.accessToken) { throw new Error('No access token available'); } const response = await System.authGetQueryToServer( `books/${bookId}`, this.accessToken ); return response.data || null; } } /** * Save or update a book */ static async saveBook(book: BookProps | BookListProps, authorId?: string): Promise { if (this.isOffline) { // Save to local database if (typeof window === 'undefined' || !(window as any).electron) { throw new Error('Electron API not available'); } const result = await (window as any).electron.dbSaveBook(book, authorId); if (!result.success) { throw new Error(result.error || 'Failed to save book to local DB'); } } else { // Save to server if (!this.accessToken) { throw new Error('No access token available'); } const isUpdate = 'bookId' in book && book.bookId; if (isUpdate) { await System.authPutToServer(`books/${book.bookId || (book as any).id}`, book, this.accessToken); } else { await System.authPostToServer('books', book, this.accessToken); } // Also save to local DB for caching if (typeof window !== 'undefined' && (window as any).electron) { await (window as any).electron.dbSaveBook(book, authorId); } } } /** * Delete a book */ static async deleteBook(bookId: string): Promise { if (this.isOffline) { // Delete from local database if (typeof window === 'undefined' || !(window as any).electron) { throw new Error('Electron API not available'); } const result = await (window as any).electron.dbDeleteBook(bookId); if (!result.success) { throw new Error(result.error || 'Failed to delete book from local DB'); } } else { // Delete from server if (!this.accessToken) { throw new Error('No access token available'); } await System.authDeleteToServer(`books/${bookId}`, {}, this.accessToken); // Also delete from local DB if (typeof window !== 'undefined' && (window as any).electron) { await (window as any).electron.dbDeleteBook(bookId); } } } // ========== CHAPTER OPERATIONS ========== /** * Save or update a chapter */ static async saveChapter(chapter: ChapterProps, bookId: string, contentId?: string): Promise { if (this.isOffline) { // Save to local database if (typeof window === 'undefined' || !(window as any).electron) { throw new Error('Electron API not available'); } const result = await (window as any).electron.dbSaveChapter(chapter, bookId, contentId); if (!result.success) { throw new Error(result.error || 'Failed to save chapter to local DB'); } } else { // Save to server if (!this.accessToken) { throw new Error('No access token available'); } const isUpdate = !!chapter.chapterId; if (isUpdate) { await System.authPutToServer(`chapters/${chapter.chapterId}`, chapter, this.accessToken); } else { await System.authPostToServer('chapters', { ...chapter, bookId }, this.accessToken); } // Also save to local DB for caching if (typeof window !== 'undefined' && (window as any).electron) { await (window as any).electron.dbSaveChapter(chapter, bookId, contentId); } } } // ========== CHARACTER OPERATIONS ========== /** * Get all characters for a book */ static async getCharacters(bookId: string): Promise { if (this.isOffline) { // Use local database if (typeof window === 'undefined' || !(window as any).electron) { throw new Error('Electron API not available'); } const result = await (window as any).electron.dbGetCharacters(bookId); if (!result.success) { throw new Error(result.error || 'Failed to get characters from local DB'); } return result.data || []; } else { // Use server API if (!this.accessToken) { throw new Error('No access token available'); } const response = await System.authGetQueryToServer( `characters?bookId=${bookId}`, this.accessToken ); return response.data || []; } } /** * Save or update a character */ static async saveCharacter(character: CharacterProps, bookId: string): Promise { if (this.isOffline) { // Save to local database if (typeof window === 'undefined' || !(window as any).electron) { throw new Error('Electron API not available'); } const result = await (window as any).electron.dbSaveCharacter(character, bookId); if (!result.success) { throw new Error(result.error || 'Failed to save character to local DB'); } } else { // Save to server if (!this.accessToken) { throw new Error('No access token available'); } const isUpdate = !!character.id; if (isUpdate) { await System.authPutToServer(`characters/${character.id}`, character, this.accessToken); } else { await System.authPostToServer('characters', { ...character, bookId }, this.accessToken); } // Also save to local DB for caching if (typeof window !== 'undefined' && (window as any).electron) { await (window as any).electron.dbSaveCharacter(character, bookId); } } } // ========== AI CONVERSATION OPERATIONS ========== /** * Get all AI conversations for a book */ static async getConversations(bookId: string): Promise { if (this.isOffline) { // Use local database if (typeof window === 'undefined' || !(window as any).electron) { throw new Error('Electron API not available'); } const result = await (window as any).electron.dbGetConversations(bookId); if (!result.success) { throw new Error(result.error || 'Failed to get conversations from local DB'); } return result.data || []; } else { // Use server API if (!this.accessToken) { throw new Error('No access token available'); } const response = await System.authGetQueryToServer( `ai/conversations?bookId=${bookId}`, this.accessToken ); return response.data || []; } } /** * Save an AI conversation (always saves locally when using AI) */ static async saveConversation(conversation: Conversation, bookId: string): Promise { // Always save AI conversations to local DB first if (typeof window !== 'undefined' && (window as any).electron) { const result = await (window as any).electron.dbSaveConversation(conversation, bookId); if (!result.success) { console.error('Failed to save conversation to local DB:', result.error); } } // If online, also sync to server if (!this.isOffline && this.accessToken) { try { const isUpdate = !!conversation.id; if (isUpdate) { await System.authPutToServer( `ai/conversations/${conversation.id}`, conversation, this.accessToken ); } else { await System.authPostToServer( 'ai/conversations', { ...conversation, bookId }, this.accessToken ); } } catch (error) { console.warn('Failed to sync conversation to server:', error); // Don't throw - local save succeeded } } } // ========== SYNC STATUS ========== /** * Get sync status from local database */ static async getSyncStatus(): Promise { if (typeof window === 'undefined' || !(window as any).electron) { return []; } const result = await (window as any).electron.dbGetSyncStatus(); if (!result.success) { console.error('Failed to get sync status:', result.error); return []; } return result.data || []; } /** * Get pending changes awaiting sync */ static async getPendingChanges(limit: number = 100): Promise { if (typeof window === 'undefined' || !(window as any).electron) { return []; } const result = await (window as any).electron.dbGetPendingChanges(limit); if (!result.success) { console.error('Failed to get pending changes:', result.error); return []; } return result.data || []; } } export default DataService;