import { Database, QueryResult, RunResult, SQLiteValue } from "node-sqlite3-wasm"; import System from "@/electron/database/System"; export interface BookIssuesTable extends Record { issue_id: string; author_id: string; book_id: string; name: string; hashed_issue_name: string; last_update: number; } export interface SyncedIssueResult extends Record { issue_id: string; book_id: string; name: string; last_update: number; } export interface IssueQuery extends Record { issue_id: string; name: string; } export default class IssueRepository { /** * Fetches all issues associated with a specific book. * @param userId - The unique identifier of the user/author. * @param bookId - The unique identifier of the book. * @param lang - The language for error messages ('fr' or 'en'). * @returns An array of issues with their IDs and names. */ public static fetchIssuesFromBook(userId: string, bookId: string, lang: 'fr' | 'en'): IssueQuery[] { try { const db: Database = System.getDb(); const query: string = 'SELECT issue_id, name FROM book_issues WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const issues: IssueQuery[] = db.all(query, params) as IssueQuery[]; return issues; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les problématiques.` : `Unable to retrieve issues.`); } 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 issue into the database after verifying it doesn't already exist. * @param issueId - The unique identifier for the new issue. * @param userId - The unique identifier of the user/author. * @param bookId - The unique identifier of the book. * @param encryptedName - The encrypted name of the issue. * @param hashedName - The hashed name of the issue for duplicate checking. * @param lang - The language for error messages ('fr' or 'en'). * @returns The issue ID if successfully inserted. */ public static insertNewIssue(issueId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en'): string { let existingIssue: QueryResult | null; let insertResult: RunResult; try { const db: Database = System.getDb(); const checkQuery: string = 'SELECT issue_id FROM book_issues WHERE hashed_issue_name=? AND book_id=? AND author_id=?'; const checkParams: SQLiteValue[] = [hashedName, bookId, userId]; existingIssue = 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 de la problématique.` : `Unable to verify issue existence.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } if (existingIssue !== null) { throw new Error(lang === 'fr' ? `La problématique existe déjà.` : `This issue already exists.`); } try { const db: Database = System.getDb(); const insertQuery: string = 'INSERT INTO book_issues (issue_id, author_id, book_id, name, hashed_issue_name, last_update) VALUES (?, ?, ?, ?, ?, ?)'; const insertParams: SQLiteValue[] = [issueId, userId, bookId, encryptedName, hashedName, 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 la problématique.` : `Unable to add issue.`); } 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' ? `Erreur pendant l'ajout de la problématique.` : `Error adding issue.`); } return issueId; } /** * Deletes an issue from the database. * @param userId - The unique identifier of the user/author. * @param issueId - The unique identifier of the issue to delete. * @param lang - The language for error messages ('fr' or 'en'). * @returns True if the issue was successfully deleted, false otherwise. */ public static deleteIssue(userId: string, issueId: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM book_issues WHERE author_id=? AND issue_id=?'; const params: SQLiteValue[] = [userId, issueId]; 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 la problématique.` : `Unable to delete issue.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all complete issue records for a specific book. * @param userId - The unique identifier of the user/author. * @param bookId - The unique identifier of the book. * @param lang - The language for error messages ('fr' or 'en'). * @returns A promise resolving to an array of complete issue records. */ static async fetchBookIssues(userId: string, bookId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT issue_id, author_id, book_id, name, hashed_issue_name, last_update FROM book_issues WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const issues: BookIssuesTable[] = db.all(query, params) as BookIssuesTable[]; return issues; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les problématiques.` : `Unable to retrieve issues.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced issues for a specific user. * @param userId - The unique identifier of the user/author. * @param lang - The language for error messages ('fr' or 'en'). * @returns An array of synced issue records. */ static fetchSyncedIssues(userId: string, lang: 'fr' | 'en'): SyncedIssueResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT issue_id, book_id, name, last_update FROM book_issues WHERE author_id = ?'; const params: SQLiteValue[] = [userId]; const syncedIssues: SyncedIssueResult[] = db.all(query, params) as SyncedIssueResult[]; return syncedIssues; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les problématiques synchronisées.` : `Unable to retrieve synced issues.`); } 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 issue from remote into the local database. * @param issueId - The unique identifier of the issue. * @param authorId - The unique identifier of the author. * @param bookId - The unique identifier of the book. * @param name - The encrypted name of the issue. * @param hashedIssueName - The hashed name of the issue. * @param lastUpdate - The timestamp of the last update. * @param lang - The language for error messages ('fr' or 'en'). * @returns True if the issue was successfully inserted, false otherwise. */ static insertSyncIssue(issueId: string, authorId: string, bookId: string, name: string, hashedIssueName: string, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = `INSERT INTO book_issues (issue_id, author_id, book_id, name, hashed_issue_name, last_update) VALUES (?, ?, ?, ?, ?, ?)`; const params: SQLiteValue[] = [issueId, authorId, bookId, name, hashedIssueName, 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 la problématique.` : `Unable to insert issue.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches a complete issue record by its ID. * @param id - The unique identifier of the issue. * @param lang - The language for error messages ('fr' or 'en'). * @returns A promise resolving to an array of complete issue records. */ static async fetchCompleteIssueById(id: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = `SELECT issue_id, author_id, book_id, name, hashed_issue_name, last_update FROM book_issues WHERE issue_id = ?`; const params: SQLiteValue[] = [id]; const issues: BookIssuesTable[] = db.all(query, params) as BookIssuesTable[]; return issues; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer le problème complet.` : `Unable to retrieve complete issue.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates an existing issue in the database. * @param userId - The unique identifier of the user/author. * @param bookId - The unique identifier of the book. * @param issueId - The unique identifier of the issue to update. * @param name - The new encrypted name of the issue. * @param hashedName - The new hashed name of the issue. * @param lastUpdate - The timestamp of the update. * @param lang - The language for error messages ('fr' or 'en'). * @returns True if the issue was successfully updated, false otherwise. */ static updateIssue(userId: string, bookId: string, issueId: string, name: string, hashedName: string, lastUpdate: number, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = `UPDATE book_issues SET name = ?, hashed_issue_name = ?, last_update = ? WHERE issue_id = ? AND author_id = ? AND book_id = ?`; const params: SQLiteValue[] = [name, hashedName, lastUpdate, issueId, 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 la problématique.` : `Unable to update issue.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if an issue exists in the database. * @param userId - The unique identifier of the user/author. * @param bookId - The unique identifier of the book. * @param issueId - The unique identifier of the issue to check. * @param lang - The language for error messages ('fr' or 'en'). * @returns True if the issue exists, false otherwise. */ static issueExist(userId: string, bookId: string, issueId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM book_issues WHERE issue_id=? AND author_id=? AND book_id=?'; const params: SQLiteValue[] = [issueId, userId, bookId]; const existingIssue: QueryResult | null = db.get(query, params) || null; return existingIssue !== 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 problème.` : `Unable to check issue existence.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } }