import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm'; import System from "../System.js"; export interface BookQuery extends Record { book_id: string; type: string; author_id: string; title: string; hashed_title: string; sub_title: string | null; hashed_sub_title: string | null; summary: string | null; serie_id: number | null; desired_release_date: string | null; desired_word_count: number | null; words_count: number | null; cover_image: string | null; } export interface EritBooksTable extends Record { book_id: string; type: string; author_id: string; title: string; hashed_title: string; sub_title: string | null; hashed_sub_title: string | null; summary: string | null; serie_id: number | null; desired_release_date: string | null; desired_word_count: number | null; words_count: number | null; last_update: number; cover_image: string | null; } export interface SyncedBookResult extends Record { book_id: string; type: string; title: string; sub_title: string | null; last_update: number; } export interface BookCoverQuery extends Record { cover_image: string; } export interface BookToolsTable extends Record { book_id: string; user_id: string; characters_enabled: number; worlds_enabled: number; locations_enabled: number; } export interface BookToolsSettings { characters: boolean; worlds: boolean; locations: boolean; } export default class BookRepo { /** * Retrieves all books for a user. * @param userId - The user identifier * @param lang - The language for error messages * @returns List of user's books */ public static fetchBooks(userId: string, lang: 'fr' | 'en'): BookQuery[] { try { const db: Database = System.getDb(); const query: string = 'SELECT book_id, type, author_id, title, sub_title, summary, serie_id, desired_release_date, desired_word_count, words_count, cover_image FROM erit_books WHERE author_id = ? ORDER BY book_id DESC'; const params: SQLiteValue[] = [userId]; return db.all(query, params) as BookQuery[]; } catch (error: unknown) { if (error instanceof Error) { console.error(error.message); throw new Error(lang === 'fr' ? 'Impossible de récupérer la liste des livres.' : 'Unable to retrieve book list.'); } console.error(error); throw new Error(lang === 'fr' ? 'Une erreur inconnue est survenue.' : 'An unknown error occurred.'); } } /** * Updates a book's cover image. * @param bookId - The book identifier * @param coverImageName - The cover image file name * @param userId - The user identifier * @param lang - The language for error messages * @returns true if the update was successful */ public static updateBookCover(bookId: string, coverImageName: string, userId: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE erit_books SET cover_image=?, last_update=? WHERE book_id=? AND author_id=?'; const params: SQLiteValue[] = [coverImageName, System.timeStampInSeconds(), bookId, userId]; const updateResult: RunResult = db.run(query, params); return updateResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de mettre à jour la couverture du livre.' : 'Unable to update book cover.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Retrieves a book by its identifier. * @param bookId - The book identifier * @param userId - The user identifier * @param lang - The language for error messages * @returns The book information */ public static fetchBook(bookId: string, userId: string, lang: 'fr' | 'en'): BookQuery { let book: BookQuery; try { const db: Database = System.getDb(); const query: string = 'SELECT book_id, author_id, title, summary, sub_title, cover_image, desired_release_date, desired_word_count, words_count FROM erit_books WHERE book_id=? AND author_id=?'; const params: SQLiteValue[] = [bookId, userId]; book = db.get(query, params) as BookQuery; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de récupérer les informations du livre.' : 'Unable to retrieve book information.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } if (!book) { throw new Error(lang === 'fr' ? 'Livre non trouvé.' : 'Book not found.'); } return book; } /** * Verifies if a book already exists for a user. * @param hashedTitle - The hashed book title * @param hashedSubTitle - The hashed book subtitle * @param userId - The user identifier * @param lang - The language for error messages * @returns true if the book exists */ public static verifyBookExist(hashedTitle: string, hashedSubTitle: string, userId: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT book_id FROM erit_books WHERE hashed_title=? AND author_id=? AND hashed_sub_title=?'; const params: SQLiteValue[] = [hashedTitle, userId, hashedSubTitle]; const book: QueryResult | null = db.get(query, params); return book !== null; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? "Impossible de vérifier l'existence du livre." : 'Unable to verify book existence.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Inserts a new book into the database. * @param bookId - The book identifier * @param userId - The user identifier * @param encryptedTitle - The encrypted title * @param hashedTitle - The hashed title * @param encryptedSubTitle - The encrypted subtitle * @param hashedSubTitle - The hashed subtitle * @param encryptedSummary - The encrypted summary * @param type - The book type * @param serie - The series identifier * @param publicationDate - The desired publication date * @param desiredWordCount - The desired word count * @param lang - The language for error messages * @returns The created book identifier */ public static insertBook(bookId: string, userId: string, encryptedTitle: string, hashedTitle: string, encryptedSubTitle: string, hashedSubTitle: string, encryptedSummary: string, type: string, serie: number, publicationDate: string, desiredWordCount: number, lang: 'fr' | 'en'): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = 'INSERT INTO erit_books (book_id, type, author_id, title, hashed_title, sub_title, hashed_sub_title, summary, serie_id, desired_release_date, desired_word_count, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)'; const params: SQLiteValue[] = [bookId, type, userId, encryptedTitle, hashedTitle, encryptedSubTitle, hashedSubTitle, encryptedSummary, serie, publicationDate ? publicationDate : null, desiredWordCount, System.timeStampInSeconds()]; insertResult = db.run(query, params); } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? "Impossible d'ajouter le livre." : 'Unable to add book.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } if (!insertResult || insertResult.changes === 0) { throw new Error(lang === 'fr' ? "Erreur lors de l'ajout du livre." : 'Error adding book.'); } return bookId; } /** * Retrieves a book's cover image. * @param userId - The user identifier * @param bookId - The book identifier * @param lang - The language for error messages * @returns The cover information */ public static fetchBookCover(userId: string, bookId: string, lang: 'fr' | 'en'): BookCoverQuery { try { const db: Database = System.getDb(); const query: string = 'SELECT cover_image FROM erit_books WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; return db.get(query, params) as BookCoverQuery; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de récupérer la couverture du livre.' : 'Unable to retrieve book cover.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Updates a book's basic information. * @param userId - The user identifier * @param title - The new title * @param hashedTitle - The hashed title * @param subTitle - The new subtitle * @param hashedSubTitle - The hashed subtitle * @param summary - The new summary * @param publicationDate - The new publication date * @param wordCount - The new desired word count * @param bookId - The book identifier * @param lang - The language for error messages * @returns true if the update was successful */ static updateBookBasicInformation(userId: string, title: string, hashedTitle: string, subTitle: string, hashedSubTitle: string, summary: string, publicationDate: string, wordCount: number, bookId: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE erit_books SET title=?, hashed_title=?, sub_title=?, hashed_sub_title=?, summary=?, serie_id=?, desired_release_date=?, desired_word_count=?, last_update=? WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [title, hashedTitle, subTitle, hashedSubTitle, summary, 0, publicationDate ? System.dateToMySqlDate(publicationDate) : null, wordCount, System.timeStampInSeconds(), userId, bookId]; const updateResult: RunResult = db.run(query, params); return updateResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de mettre à jour les informations du livre.' : 'Unable to update book information.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Deletes a book from the database. * @param userId - The user identifier * @param bookId - The book identifier to delete * @param lang - The language for error messages * @returns true if the deletion was successful */ public static deleteBook(userId: string, bookId: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM erit_books WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const deleteResult: RunResult = db.run(query, params); return deleteResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de supprimer le livre.' : 'Unable to delete book.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Retrieves all columns from erit_books table for a book. * @param userId - The user identifier * @param bookId - The book identifier * @param lang - The language for error messages * @returns The complete book data */ static async fetchEritBooksTable(userId: string, bookId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT book_id, type, author_id, title, hashed_title, sub_title, hashed_sub_title, summary, serie_id, desired_release_date, desired_word_count, words_count, cover_image, last_update FROM erit_books WHERE book_id=? AND author_id=?'; const params: SQLiteValue[] = [bookId, userId]; return db.all(query, params) as EritBooksTable[]; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de récupérer les informations du livre.' : 'Unable to retrieve book information.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Retrieves synced books for a user. * @param userId - The user identifier * @param lang - The language for error messages * @returns List of books with sync information */ static fetchSyncedBooks(userId: string, lang: 'fr' | 'en'): SyncedBookResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT book_id, type, title, sub_title, last_update FROM erit_books WHERE author_id = ?'; const params: SQLiteValue[] = [userId]; return db.all(query, params) as SyncedBookResult[]; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de récupérer les livres synchronisés.' : 'Unable to retrieve synced books.'); } console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Inserts a synced book from the server. * @param bookId - The book identifier * @param userId - The user identifier * @param type - The book type * @param title - The encrypted title * @param hashedTitle - The hashed title * @param subTitle - The encrypted subtitle * @param hashedSubTitle - The hashed subtitle * @param summary - The encrypted summary * @param serieId - The series identifier * @param desiredReleaseDate - The desired release date * @param desiredWordCount - The desired word count * @param wordsCount - The current word count * @param coverImage - The cover image file name * @param lastUpdate - The last update timestamp * @param lang - The language for error messages * @returns true if the insertion was successful */ static insertSyncBook(bookId: string, userId: string, type: string, title: string, hashedTitle: string, subTitle: string | null, hashedSubTitle: string | null, summary: string | null, serieId: number | null, desiredReleaseDate: string | null, desiredWordCount: number | null, wordsCount: number | null, coverImage: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'INSERT INTO erit_books (book_id, author_id, type, title, hashed_title, sub_title, hashed_sub_title, summary, serie_id, desired_release_date, desired_word_count, words_count, cover_image, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; const params: SQLiteValue[] = [bookId, userId, type, title, hashedTitle, subTitle, hashedSubTitle, summary, serieId, desiredReleaseDate, desiredWordCount, wordsCount, coverImage, lastUpdate]; const insertResult: RunResult = db.run(query, params); return insertResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? "Impossible d'insérer le livre synchronisé." : 'Unable to insert synced book.'); } throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Retrieves a complete book by its identifier (without author verification). * @param bookId - The book identifier * @param lang - The language for error messages * @returns The complete book data */ static async fetchCompleteBookById(bookId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT * FROM erit_books WHERE book_id = ?'; const params: SQLiteValue[] = [bookId]; return db.all(query, params) as EritBooksTable[]; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? 'Impossible de récupérer le livre complet.' : 'Unable to retrieve complete book.'); } throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } static fetchBookTools(userId: string, bookId: string, lang: 'fr' | 'en'): BookToolsTable | null { try { const db: Database = System.getDb(); const query: string = 'SELECT book_id, user_id, characters_enabled, worlds_enabled, locations_enabled FROM book_tools WHERE user_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const result = db.get(query, params) as BookToolsTable | undefined; return result ?? null; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de récupérer les paramètres des outils.' : 'Unable to fetch tools settings.'); } throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } static updateBookToolSetting(userId: string, bookId: string, toolName: 'characters_enabled' | 'worlds_enabled' | 'locations_enabled', enabled: boolean, lang: 'fr' | 'en'): boolean { const enabledValue: number = enabled ? 1 : 0; try { const db: Database = System.getDb(); const updateQuery: string = `UPDATE book_tools SET ${toolName}=? WHERE user_id=? AND book_id=?`; const updateResult: RunResult = db.run(updateQuery, [enabledValue, userId, bookId]); if (updateResult.changes > 0) { return true; } const charactersValue: number = toolName === 'characters_enabled' ? enabledValue : 0; const worldsValue: number = toolName === 'worlds_enabled' ? enabledValue : 0; const locationsValue: number = toolName === 'locations_enabled' ? enabledValue : 0; const insertQuery: string = 'INSERT INTO book_tools (book_id, user_id, characters_enabled, worlds_enabled, locations_enabled) VALUES (?, ?, ?, ?, ?)'; const insertResult: RunResult = db.run(insertQuery, [bookId, userId, charactersValue, worldsValue, locationsValue]); return insertResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? 'Impossible de mettre à jour les paramètres des outils.' : 'Unable to update tools settings.'); } throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } /** * Inserts book tools settings during sync. * @param bookId - The book identifier * @param userId - The user identifier * @param charactersEnabled - Whether characters tool is enabled * @param worldsEnabled - Whether worlds tool is enabled * @param locationsEnabled - Whether locations tool is enabled * @param lang - The language for error messages * @returns true if the insertion was successful */ static insertSyncBookTools(bookId: string, userId: string, charactersEnabled: number, worldsEnabled: number, locationsEnabled: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'INSERT INTO book_tools (book_id, user_id, characters_enabled, worlds_enabled, locations_enabled) VALUES (?, ?, ?, ?, ?)'; const params: SQLiteValue[] = [bookId, userId, charactersEnabled, worldsEnabled, locationsEnabled]; const insertResult: RunResult = db.run(query, params); return insertResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? "Impossible d'insérer les paramètres des outils." : 'Unable to insert tools settings.'); } throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } }