import { Database, QueryResult, RunResult, SQLiteValue } from "node-sqlite3-wasm"; import System from "@/electron/database/System"; export interface BookIncidentsTable extends Record { incident_id: string; author_id: string; book_id: string; title: string; hashed_title: string; summary: string | null; last_update: number; } export interface SyncedIncidentResult extends Record { incident_id: string; book_id: string; title: string; last_update: number; } export interface IncidentQuery extends Record { incident_id: string; title: string; summary: string; } export default class IncidentRepository { /** * Fetches all incidents for a specific book belonging to a user. * @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 incidents with their ID, title, and summary * @throws Error if the database query fails */ public static fetchAllIncitentIncidents(userId: string, bookId: string, lang: 'fr' | 'en'): IncidentQuery[] { try { const db: Database = System.getDb(); const query: string = 'SELECT incident_id, title, summary FROM book_incidents WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const incidents: IncidentQuery[] = db.all(query, params) as IncidentQuery[]; return incidents; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les incidents.` : `Unable to retrieve incidents.`); } 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 incident into the database. * @param incidentId - The unique ID for the new incident * @param userId - The ID of the user (author) * @param bookId - The ID of the book * @param encryptedName - The encrypted title of the incident * @param hashedName - The hashed title of the incident * @param lang - The language for error messages ('fr' or 'en') * @returns The incident ID if insertion was successful * @throws Error if the database insertion fails */ public static insertNewIncident(incidentId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en'): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = 'INSERT INTO book_incidents (incident_id,author_id, book_id, title, hashed_title, last_update) VALUES (?,?,?,?,?,?)'; const params: SQLiteValue[] = [incidentId, userId, bookId, encryptedName, hashedName, 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 l'élément déclencheur.` : `Unable to add incident.`); } 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 de l'élément déclencheur.` : `Error adding incident.`); } return incidentId; } /** * Deletes an incident from the database. * @param userId - The ID of the user (author) * @param bookId - The ID of the book * @param incidentId - The ID of the incident to delete * @param lang - The language for error messages ('fr' or 'en') * @returns True if the incident was deleted, false otherwise * @throws Error if the database deletion fails */ public static deleteIncident(userId: string, bookId: string, incidentId: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM book_incidents WHERE author_id=? AND book_id=? AND incident_id=?'; const params: SQLiteValue[] = [userId, bookId, incidentId]; 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 l'élément déclencheur.` : `Unable to delete incident.`); } 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 incident in the database. * @param userId - The ID of the user (author) * @param bookId - The ID of the book * @param incidentId - The ID of the incident to update * @param encryptedIncidentName - The new encrypted title * @param incidentHashedName - The new hashed title * @param incidentSummary - 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 incident was updated, false otherwise * @throws Error if the database update fails */ public static updateIncident(userId: string, bookId: string, incidentId: string, encryptedIncidentName: string, incidentHashedName: string, incidentSummary: string, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE book_incidents SET title=?, hashed_title=?, summary=?, last_update=? WHERE author_id=? AND book_id=? AND incident_id=?'; const params: SQLiteValue[] = [encryptedIncidentName, incidentHashedName, incidentSummary, lastUpdate, userId, bookId, incidentId]; 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 l'incident.` : `Unable to update incident.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all incidents for a book with complete information. * @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 incident records * @throws Error if the database query fails */ static async fetchBookIncidents(userId: string, bookId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT incident_id, author_id, book_id, title, hashed_title, summary, last_update FROM book_incidents WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const incidents: BookIncidentsTable[] = db.all(query, params) as BookIncidentsTable[]; return incidents; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les incidents.` : `Unable to retrieve incidents.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced incidents 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 incident records with minimal information * @throws Error if the database query fails */ static fetchSyncedIncidents(userId: string, lang: 'fr' | 'en'): SyncedIncidentResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT incident_id, book_id, title, last_update FROM book_incidents WHERE author_id = ?'; const params: SQLiteValue[] = [userId]; const syncedIncidents: SyncedIncidentResult[] = db.all(query, params) as SyncedIncidentResult[]; return syncedIncidents; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les incidents synchronisés.` : `Unable to retrieve synced incidents.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a synced incident into the database. * @param incidentId - The unique ID for the incident * @param authorId - The ID of the author * @param bookId - The ID of the book * @param title - The encrypted title * @param hashedTitle - The hashed title * @param summary - The encrypted summary (can be null) * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the incident was inserted, false otherwise * @throws Error if the database insertion fails */ static insertSyncIncident(incidentId: string, authorId: string, bookId: string, title: string, hashedTitle: string, summary: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = `INSERT INTO book_incidents (incident_id, author_id, book_id, title, hashed_title, summary, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)`; const params: SQLiteValue[] = [incidentId, authorId, bookId, title, hashedTitle, summary, 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 l'incident.` : `Unable to insert incident.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches complete incident information by its ID. * @param id - The ID of the incident to fetch * @param lang - The language for error messages ('fr' or 'en') * @returns An array containing the incident record (empty if not found) * @throws Error if the database query fails */ static async fetchCompleteIncidentById(id: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = `SELECT incident_id, author_id, book_id, title, hashed_title, summary, last_update FROM book_incidents WHERE incident_id = ?`; const params: SQLiteValue[] = [id]; const incident: BookIncidentsTable[] = db.all(query, params) as BookIncidentsTable[]; return incident; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer l'incident complet.` : `Unable to retrieve complete incident.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if an incident exists in the database. * @param userId - The ID of the user (author) * @param bookId - The ID of the book * @param incidentId - The ID of the incident to check * @param lang - The language for error messages ('fr' or 'en') * @returns True if the incident exists, false otherwise * @throws Error if the database query fails */ static incidentExist(userId: string, bookId: string, incidentId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM book_incidents WHERE book_id=? AND incident_id=? AND author_id=?'; const params: SQLiteValue[] = [bookId, incidentId, userId]; const existingIncident: QueryResult | null = db.get(query, params) || null; return existingIncident !== 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 de l'incident.` : `Unable to check incident existence.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } }