import { Database, QueryResult, RunResult, SQLiteValue } from "node-sqlite3-wasm"; import System from "../System.js"; export interface BookPlotPointsTable extends Record { plot_point_id: string; title: string; hashed_title: string; summary: string | null; linked_incident_id: string | null; author_id: string; book_id: string; last_update: number; } export interface SyncedPlotPointResult extends Record { plot_point_id: string; book_id: string; title: string; last_update: number; } export interface PlotPointQuery extends Record { plot_point_id: string; title: string; summary: string; linked_incident_id: string | null; } export default class PlotPointRepository { /** * Fetches all plot points for a specific 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 plot points with their basic information */ public static fetchAllPlotPoints(userId: string, bookId: string, lang: 'fr' | 'en'): PlotPointQuery[] { try { const db: Database = System.getDb(); const query: string = 'SELECT plot_point_id, title, summary, linked_incident_id FROM book_plot_points WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const plotPoints: PlotPointQuery[] = db.all(query, params) as PlotPointQuery[]; return plotPoints; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les points d'intrigue.` : `Unable to retrieve plot points.`); } 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 plot point into the database. * @param plotPointId - The unique ID for the new plot point * @param userId - The ID of the user/author * @param bookId - The ID of the book * @param encryptedName - The encrypted title of the plot point * @param hashedName - The hashed title for duplicate checking * @param incidentId - The ID of the linked incident (can be empty string) * @param lang - The language for error messages ('fr' or 'en') * @returns The ID of the newly created plot point */ static insertNewPlotPoint(plotPointId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, incidentId: string, lang: 'fr' | 'en'): string { let existingPlotPoint: QueryResult | null; let insertResult: RunResult; try { const db: Database = System.getDb(); const checkQuery: string = 'SELECT plot_point_id FROM book_plot_points WHERE author_id=? AND book_id=? AND hashed_title=?'; const checkParams: SQLiteValue[] = [userId, bookId, hashedName]; existingPlotPoint = db.get(checkQuery, checkParams); } 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 point d'intrigue.` : `Unable to verify plot point existence.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } if (existingPlotPoint !== null) { throw new Error(lang === 'fr' ? `Ce point de l'intrigue existe déjà.` : `This plot point already exists.`); } try { const db: Database = System.getDb(); const insertQuery: string = 'INSERT INTO book_plot_points (plot_point_id,title,hashed_title,author_id,book_id,linked_incident_id,last_update) VALUES (?,?,?,?,?,?,?)'; const insertParams: SQLiteValue[] = [plotPointId, encryptedName, hashedName, userId, bookId, incidentId, System.timeStampInSeconds()]; insertResult = db.run(insertQuery, insertParams); } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible d'ajouter le point d'intrigue.` : `Unable to add plot point.`); } else { 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' ? `Une erreur s'est produite lors de l'ajout du point d'intrigue.` : `Error adding plot point.`); } return plotPointId; } /** * Deletes a plot point from the database. * @param userId - The ID of the user/author * @param plotPointId - The ID of the plot point to delete * @param lang - The language for error messages ('fr' or 'en') * @returns True if the plot point was deleted, false otherwise */ static deletePlotPoint(userId: string, plotPointId: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM book_plot_points WHERE author_id=? AND plot_point_id=?'; const params: SQLiteValue[] = [userId, plotPointId]; 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 point d'intrigue.` : `Unable to delete plot point.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates an existing plot point in the database. * @param userId - The ID of the user/author * @param bookId - The ID of the book * @param plotPointId - The ID of the plot point to update * @param encryptedPlotPointName - The new encrypted title * @param plotPointHashedName - The new hashed title * @param plotPointSummary - The new summary * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the plot point was updated, false otherwise */ public static updatePlotPoint(userId: string, bookId: string, plotPointId: string, encryptedPlotPointName: string, plotPointHashedName: string, plotPointSummary: string, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE book_plot_points SET title=?, hashed_title=?, summary=?, last_update=? WHERE author_id=? AND book_id=? AND plot_point_id=?'; const params: SQLiteValue[] = [encryptedPlotPointName, plotPointHashedName, plotPointSummary, lastUpdate, userId, bookId, plotPointId]; 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 le point d'intrigue.` : `Unable to update plot point.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all plot points for a book with complete information for synchronization. * @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 complete plot point records */ static async fetchBookPlotPoints(userId: string, bookId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT plot_point_id, title, hashed_title, summary, linked_incident_id, author_id, book_id, last_update FROM book_plot_points WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const plotPoints: BookPlotPointsTable[] = db.all(query, params) as BookPlotPointsTable[]; return plotPoints; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les points d'intrigue.` : `Unable to retrieve plot points.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced plot points for a user across all books. * @param userId - The ID of the user/author * @param lang - The language for error messages ('fr' or 'en') * @returns An array of synced plot point records with minimal information */ static fetchSyncedPlotPoints(userId: string, lang: 'fr' | 'en'): SyncedPlotPointResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT plot_point_id, book_id, title, last_update FROM book_plot_points WHERE author_id = ?'; const params: SQLiteValue[] = [userId]; const syncedPlotPoints: SyncedPlotPointResult[] = db.all(query, params) as SyncedPlotPointResult[]; return syncedPlotPoints; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les points d'intrigue synchronisés.` : `Unable to retrieve synced plot points.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a plot point during synchronization from remote data. * @param plotPointId - The unique ID of the plot point * @param title - The encrypted title * @param hashedTitle - The hashed title for duplicate checking * @param summary - The encrypted summary (can be null) * @param linkedIncidentId - The ID of the linked incident (can be null) * @param authorId - The ID of the author * @param bookId - The ID of the book * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the plot point was inserted, false otherwise */ static insertSyncPlotPoint(plotPointId: string, title: string, hashedTitle: string, summary: string | null, linkedIncidentId: string | null, authorId: string, bookId: string, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = `INSERT INTO book_plot_points (plot_point_id, title, hashed_title, summary, linked_incident_id, author_id, book_id, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`; const params: SQLiteValue[] = [plotPointId, title, hashedTitle, summary, linkedIncidentId, authorId, bookId, 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 point d'intrigue.` : `Unable to insert plot point.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches complete plot point data by its ID. * @param plotPointId - The ID of the plot point to retrieve * @param lang - The language for error messages ('fr' or 'en') * @returns An array containing the plot point data (empty if not found) */ static async fetchCompletePlotPointById(plotPointId: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = `SELECT plot_point_id, title, hashed_title, summary, linked_incident_id, author_id, book_id, last_update FROM book_plot_points WHERE plot_point_id = ?`; const params: SQLiteValue[] = [plotPointId]; const plotPoint: BookPlotPointsTable[] = db.all(query, params) as BookPlotPointsTable[]; return plotPoint; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer le point d'intrigue complet.` : `Unable to retrieve complete plot point.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if a plot point exists in the database. * @param userId - The ID of the user/author * @param bookId - The ID of the book * @param plotPointId - The ID of the plot point to check * @param lang - The language for error messages ('fr' or 'en') * @returns True if the plot point exists, false otherwise */ static plotPointExist(userId: string, bookId: string, plotPointId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM book_plot_points WHERE author_id =? AND book_id =? AND plot_point_id =?'; const params: SQLiteValue[] = [userId, bookId, plotPointId]; const existingPlotPoint: QueryResult | null = db.get(query, params) || null; return existingPlotPoint !== 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 point de intrigue.` : `Unable to check plot point existence.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } }