import {Database, QueryResult, RunResult, SQLiteValue} from "node-sqlite3-wasm"; import System from "../System.js"; export interface ChapterContentQueryResult extends Record { chapter_id: string; version: number; content: string; words_count: number; title: string; chapter_order: number; } export interface ContentQueryResult extends Record { content: string; } export interface CompanionContentQueryResult extends Record { version: number; content: string; words_count: number; } export interface BookChapterContentTable extends Record { content_id: string; chapter_id: string; author_id: string; version: number; content: string | null; words_count: number; time_on_it: number; last_update: number; } export interface SyncedChapterContentResult extends Record { content_id: string; chapter_id: string; last_update: number; } export default class ChapterContentRepository { /** * Fetches the last chapter content for a given book. * @param userId - The ID of the user/author. * @param bookId - The ID of the book. * @param lang - The language for error messages ('fr' or 'en'). * @returns An array of chapter content results ordered by chapter order and version descending. */ public static fetchLastChapterContent(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterContentQueryResult[] { try { const db: Database = System.getDb(); const query: string = ` SELECT book_chapters.chapter_id as chapter_id, COALESCE(book_chapter_content.version, 2) AS version, COALESCE(book_chapter_content.content, '') AS content, COALESCE(book_chapter_content.words_count, 0) AS words_count, book_chapters.title, book_chapters.chapter_order FROM book_chapters LEFT JOIN book_chapter_content ON book_chapters.chapter_id = book_chapter_content.chapter_id WHERE book_chapters.author_id = ? AND book_chapters.book_id = ? ORDER BY book_chapters.chapter_order DESC, book_chapter_content.version DESC LIMIT 1 `; const params: SQLiteValue[] = [userId, bookId]; const chapterContents: ChapterContentQueryResult[] = db.all(query, params) as ChapterContentQueryResult[]; return chapterContents; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer le dernier chapitre.` : `Unable to retrieve last chapter.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates the content of a chapter. If no existing content is found, inserts a new record. * @param userId - The ID of the user/author. * @param chapterId - The ID of the chapter. * @param version - The version number of the content. * @param encryptContent - The encrypted content string. * @param wordsCount - The word count of the content. * @param lastUpdate - The timestamp of the last update. * @param lang - The language for error messages ('fr' or 'en'). * @returns True if the update or insert was successful. */ public static updateChapterContent(userId: string, chapterId: string, version: number, encryptContent: string, wordsCount: number, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const updateQuery: string = 'UPDATE book_chapter_content SET content=?, words_count=?, last_update=? WHERE chapter_id=? AND author_id=? AND version=?'; const updateParams: SQLiteValue[] = [encryptContent, wordsCount, lastUpdate, chapterId, userId, version]; const updateResult: RunResult = db.run(updateQuery, updateParams); if (updateResult.changes > 0) { return true; } else { const contentId: string = System.createUniqueId(); const insertQuery: string = 'INSERT INTO book_chapter_content (content_id, chapter_id, author_id, version, content, words_count, last_update) VALUES (?,?,?,?,?,?,?)'; const insertParams: SQLiteValue[] = [contentId, chapterId, userId, version, encryptContent, wordsCount, lastUpdate]; const insertResult: RunResult = db.run(insertQuery, insertParams); 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 le contenu du chapitre.` : `Unable to update chapter content.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches companion content for a specific chapter and version. * @param userId - The ID of the user/author. * @param chapterId - The ID of the chapter. * @param version - The version number to fetch. * @param lang - The language for error messages ('fr' or 'en'). * @returns An array of companion content results. */ static fetchCompanionContent(userId: string, chapterId: string, version: number, lang: 'fr' | 'en' = 'fr'): CompanionContentQueryResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT version, content, words_count FROM book_chapter_content WHERE author_id=? AND chapter_id=? AND version=?'; const params: SQLiteValue[] = [userId, chapterId, version]; const companionContents: CompanionContentQueryResult[] = db.all(query, params) as CompanionContentQueryResult[]; return companionContents; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu compagnon.` : `Unable to retrieve companion content.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches chapter content by its order position within a book. * @param userId - The ID of the user/author. * @param chapterOrder - The order position of the chapter. * @param bookId - The ID of the book. * @param lang - The language for error messages ('fr' or 'en'). * @returns The content query result for the specified chapter. * @throws Error if no chapter is found with the specified order. */ static fetchChapterContentByChapterOrder(userId: string, chapterOrder: number, bookId: string, lang: 'fr' | 'en' = 'fr'): ContentQueryResult { let chapterContent: ContentQueryResult | null; try { const db: Database = System.getDb(); const query: string = ` SELECT content.content FROM book_chapters as chapter INNER JOIN book_chapter_content AS content ON chapter.chapter_id=content.chapter_id WHERE chapter.chapter_order=? AND content.version=2 AND chapter.book_id=? AND chapter.author_id=? `; const params: SQLiteValue[] = [chapterOrder, bookId, userId]; chapterContent = db.get(query, params) as ContentQueryResult | null; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu du chapitre.` : `Unable to retrieve chapter content.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } if (!chapterContent) { throw new Error(lang === 'fr' ? `Aucun chapitre trouvé avec cet ordre.` : `No chapter found with this order.`); } return chapterContent; } /** * Fetches chapter content by chapter ID and version number. * @param userId - The ID of the user/author. * @param chapterId - The ID of the chapter. * @param version - The version number to fetch. * @param lang - The language for error messages ('fr' or 'en'). * @returns The content query result for the specified version. * @throws Error if no chapter is found with the specified version. */ static fetchChapterContentByVersion(userId: string, chapterId: string, version: number, lang: 'fr' | 'en' = 'fr'): ContentQueryResult { let chapterContent: ContentQueryResult | null; try { const db: Database = System.getDb(); const query: string = 'SELECT content FROM book_chapter_content WHERE author_id=? AND chapter_id=? AND version=?'; const params: SQLiteValue[] = [userId, chapterId, version]; chapterContent = db.get(query, params) as ContentQueryResult | null; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu du chapitre.` : `Unable to retrieve chapter content.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } if (!chapterContent) { throw new Error(lang === 'fr' ? `Aucun chapitre trouvé avec cette version.` : `No chapter found with this version.`); } return chapterContent; } /** * Checks whether chapter content exists for a given content ID and user. * @param userId - The ID of the user/author. * @param contentId - The ID of the content to check. * @param lang - The language for error messages ('fr' or 'en'). * @returns True if the chapter content exists, false otherwise. */ static isChapterContentExist(userId: string, contentId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM `book_chapter_content` WHERE `content_id`=? AND `author_id`=?'; const params: SQLiteValue[] = [contentId, userId]; const existenceCheck: QueryResult | null = db.get(query, params) || null; return existenceCheck !== 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 contenu du chapitre.` : `Unable to check chapter content existence.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all chapter contents for a specific chapter belonging to a user. * @param userId - The ID of the user/author. * @param chapterId - The ID of the chapter. * @param lang - The language for error messages ('fr' or 'en'). * @returns A promise resolving to an array of book chapter content records. */ static async fetchBookChapterContents(userId: string, chapterId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT content_id, chapter_id, author_id, version, content, words_count, time_on_it, last_update FROM book_chapter_content WHERE author_id=? AND chapter_id=?'; const params: SQLiteValue[] = [userId, chapterId]; return db.all(query, params) as BookChapterContentTable[]; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu des chapitres.` : `Unable to retrieve chapter contents.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced chapter contents for a user (content ID, chapter ID, and last update timestamp). * @param userId - The ID of the user/author. * @param lang - The language for error messages ('fr' or 'en'). * @returns An array of synced chapter content results. */ static fetchSyncedChapterContents(userId: string, lang: 'fr' | 'en'): SyncedChapterContentResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT content_id, chapter_id, last_update FROM book_chapter_content WHERE author_id = ?'; const params: SQLiteValue[] = [userId]; return db.all(query, params) as SyncedChapterContentResult[]; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu des chapitres synchronisés.` : `Unable to retrieve synced chapter contents.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a new chapter content record during synchronization. * @param contentId - The unique ID for the content. * @param chapterId - The ID of the chapter. * @param authorId - The ID of the author. * @param version - The version number of the content. * @param content - The content string (can be null). * @param wordsCount - The word count of the content. * @param timeOnIt - The time spent on this content. * @param lastUpdate - The timestamp of the last update. * @param lang - The language for error messages ('fr' or 'en'). * @returns True if the insert was successful. */ static insertSyncChapterContent(contentId: string, chapterId: string, authorId: string, version: number, content: string | null, wordsCount: number, timeOnIt: number, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = `INSERT INTO book_chapter_content (content_id, chapter_id, author_id, version, content, words_count, time_on_it, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`; const params: SQLiteValue[] = [contentId, chapterId, authorId, version, content, wordsCount, timeOnIt, 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 contenu du chapitre.` : `Unable to insert chapter content.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches the complete chapter content record by its content ID. * @param contentId - The ID of the content to fetch. * @param lang - The language for error messages ('fr' or 'en'). * @returns A promise resolving to an array of book chapter content records. */ static async fetchCompleteChapterContentById(contentId: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT content_id, chapter_id, author_id, version, content, words_count, time_on_it, last_update FROM book_chapter_content WHERE content_id = ?'; const params: SQLiteValue[] = [contentId]; return db.all(query, params) as BookChapterContentTable[]; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu de chapitre complet.` : `Unable to retrieve complete chapter content.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches a complete chapter with its content by joining chapters and chapter content tables. * @param userId - The ID of the user/author. * @param chapterId - The ID of the chapter. * @param version - The version number of the content to fetch. * @param lang - The language for error messages ('fr' or 'en'). * @returns The chapter content query result with chapter metadata. * @throws Error if no chapter is found with the specified ID. */ public static fetchWholeChapter(userId: string, chapterId: string, version: number, lang: 'fr' | 'en' = 'fr'): ChapterContentQueryResult { let wholeChapter: ChapterContentQueryResult | null; try { const db: Database = System.getDb(); const query: string = ` SELECT chapter.chapter_id as chapter_id, chapter.title as title, chapter.chapter_order, chapter.words_count, content.content AS content, content.version as version FROM book_chapters AS chapter LEFT JOIN book_chapter_content AS content ON content.chapter_id = chapter.chapter_id AND content.version = ? WHERE chapter.chapter_id = ? AND chapter.author_id = ? `; const params: SQLiteValue[] = [version, chapterId, userId]; wholeChapter = db.get(query, params) as ChapterContentQueryResult | null; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer le chapitre.` : `Unable to retrieve chapter.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } if (!wholeChapter) { throw new Error(lang === 'fr' ? `Aucun chapitre trouvé avec cet ID.` : `No chapter found with this ID.`); } return wholeChapter; } }