import { Database, QueryResult, RunResult, SQLiteValue } from "node-sqlite3-wasm"; import System from "../System.js"; export interface BookWorldTable extends Record { world_id: string; name: string; hashed_name: string; author_id: string; book_id: string; history: string | null; politics: string | null; economy: string | null; religion: string | null; languages: string | null; last_update: number; } export interface BookWorldElementsTable extends Record { element_id: string; world_id: string; user_id: string; element_type: number; name: string; original_name: string; description: string | null; last_update: number; } export interface SyncedWorldResult extends Record { world_id: string; book_id: string; name: string; last_update: number; } export interface SyncedWorldElementResult extends Record { element_id: string; world_id: string; name: string; last_update: number; } export interface WorldQuery extends Record { world_id: string; world_name: string; history: string | null; politics: string | null; economy: string | null; religion: string | null; languages: string | null; element_id: string | null; element_name: string | null; element_description: string | null; element_type: number | null; series_world_id: string | null; } export interface WorldElementValue { id: string; name: string; description: string; type: number; } export default class WorldRepository { /** * Checks if a world with the given name exists for a specific user and book. * @param userId - The unique identifier of the user * @param bookId - The unique identifier of the book * @param worldName - The hashed name of the world to check * @param lang - The language for error messages ('fr' or 'en') * @returns True if the world exists, false otherwise */ public static checkWorldExist(userId: string, bookId: string, worldName: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT world_id FROM book_world WHERE author_id=? AND book_id=? AND hashed_name=?'; const params: SQLiteValue[] = [userId, bookId, worldName]; const world: QueryResult | null = db.get(query, params); return world !== 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 monde.` : `Unable to verify world existence.`); } 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 world into the database. * @param worldId - The unique identifier for the new world * @param userId - The unique identifier of the author * @param bookId - The unique identifier of the book * @param encryptedName - The encrypted name of the world * @param hashedName - The hashed name of the world for uniqueness checks * @param lang - The language for error messages ('fr' or 'en') * @returns The world ID if insertion was successful */ public static insertNewWorld(worldId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en', seriesWorldId: string | null = null): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = seriesWorldId ? 'INSERT INTO book_world (world_id, author_id, book_id, name, hashed_name, series_world_id, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)' : 'INSERT INTO book_world (world_id, author_id, book_id, name, hashed_name, last_update) VALUES (?, ?, ?, ?, ?, ?)'; const params: SQLiteValue[] = seriesWorldId ? [worldId, userId, bookId, encryptedName, hashedName, seriesWorldId, System.timeStampInSeconds()] : [worldId, 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 le monde.` : `Unable to add world.`); } 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 lors de l'ajout du monde.` : `Error adding world.`); } return worldId; } /** * Fetches all worlds and their elements for a specific user and book. * @param userId - The unique identifier of the user * @param bookId - The unique identifier of the book * @param lang - The language for error messages ('fr' or 'en') * @returns An array of world query results with joined element data */ public static fetchWorlds(userId: string, bookId: string, lang: 'fr' | 'en'): WorldQuery[] { try { const db: Database = System.getDb(); const query: string = 'SELECT world.world_id AS world_id, world.name AS world_name, world.history, world.politics, world.economy, world.religion, world.languages, element.element_id AS element_id, element.name AS element_name, element.description AS element_description, element.element_type, world.series_world_id FROM book_world AS world LEFT JOIN book_world_elements AS element ON world.world_id=element.world_id WHERE world.author_id=? AND world.book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const worlds: WorldQuery[] = db.all(query, params) as WorldQuery[]; return worlds; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les mondes.` : `Unable to retrieve worlds.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates a world's data in the database. * @param userId - The unique identifier of the author * @param worldId - The unique identifier of the world to update * @param encryptName - The new encrypted name * @param hashedName - The new hashed name * @param encryptHistory - The new encrypted history * @param encryptPolitics - The new encrypted politics * @param encryptEconomy - The new encrypted economy * @param encryptReligion - The new encrypted religion * @param encryptLanguages - The new encrypted languages * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update was successful, false otherwise */ public static updateWorld(userId: string, worldId: string, encryptName: string, hashedName: string, encryptHistory: string, encryptPolitics: string, encryptEconomy: string, encryptReligion: string, encryptLanguages: string, lastUpdate: number, lang: 'fr' | 'en', seriesWorldId: string | null = null): boolean { try { const db: Database = System.getDb(); const query: string = seriesWorldId !== null ? 'UPDATE book_world SET name=?, hashed_name=?, history=?, politics=?, economy=?, religion=?, languages=?, last_update=?, series_world_id=? WHERE author_id=? AND world_id=?' : 'UPDATE book_world SET name=?, hashed_name=?, history=?, politics=?, economy=?, religion=?, languages=?, last_update=? WHERE author_id=? AND world_id=?'; const params: SQLiteValue[] = seriesWorldId !== null ? [encryptName, hashedName, encryptHistory, encryptPolitics, encryptEconomy, encryptReligion, encryptLanguages, lastUpdate, seriesWorldId, userId, worldId] : [encryptName, hashedName, encryptHistory, encryptPolitics, encryptEconomy, encryptReligion, encryptLanguages, lastUpdate, userId, worldId]; 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 monde.` : `Unable to update world.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates multiple world elements in the database. * @param userId - The unique identifier of the user * @param elements - An array of world element values to update * @param lang - The language for error messages ('fr' or 'en') * @returns True if all updates were successful, false otherwise */ public static updateWorldElements(userId: string, elements: WorldElementValue[], lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE book_world_elements SET name=?, description=?, element_type=?, last_update=? WHERE user_id=? AND element_id=?'; for (const element of elements) { const params: SQLiteValue[] = [element.name, element.description, element.type, System.timeStampInSeconds(), userId, element.id]; const updateResult: RunResult = db.run(query, params); if (updateResult.changes <= 0) { return false; } } return true; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de mettre à jour les éléments du monde.` : `Unable to update world elements.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if a world element with the given hashed name exists. * @param worldNumId - The unique identifier of the world * @param hashedName - The hashed name of the element to check * @param lang - The language for error messages ('fr' or 'en') * @returns True if the element exists, false otherwise */ public static checkElementExist(worldNumId: string, hashedName: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT element_id FROM book_world_elements WHERE world_id=? AND original_name=?'; const params: SQLiteValue[] = [worldNumId, hashedName]; const element: QueryResult | null = db.get(query, params); return element !== 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'élément.` : `Unable to verify element existence.`); } 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 world element into the database. * @param userId - The unique identifier of the user * @param elementId - The unique identifier for the new element * @param elementType - The type of the element * @param worldId - The unique identifier of the parent world * @param encryptedName - The encrypted name of the element * @param hashedName - The hashed name of the element for uniqueness checks * @param lang - The language for error messages ('fr' or 'en') * @returns The element ID if insertion was successful */ public static insertNewElement(userId: string, elementId: string, elementType: number, worldId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en'): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = 'INSERT INTO book_world_elements (element_id, world_id, user_id, name, original_name, element_type, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)'; const params: SQLiteValue[] = [elementId, worldId, userId, encryptedName, hashedName, elementType, 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.` : `Unable to add element.`); } 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 lors de l'ajout de l'élément.` : `Error adding element.`); } return elementId; } /** * Deletes a world element from the database. * @param userId - The unique identifier of the user * @param elementId - The unique identifier of the element to delete * @param lang - The language for error messages ('fr' or 'en') * @returns True if the deletion was successful, false otherwise */ public static deleteElement(userId: string, elementId: string, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM book_world_elements WHERE user_id=? AND element_id=?'; const params: SQLiteValue[] = [userId, elementId]; 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.` : `Unable to delete element.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all worlds for a specific user and book. * @param userId - The unique identifier of the user * @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 book world table records */ static async fetchBookWorlds(userId: string, bookId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT world_id, name, hashed_name, author_id, book_id, history, politics, economy, religion, languages, last_update FROM book_world WHERE author_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; const worlds: BookWorldTable[] = db.all(query, params) as BookWorldTable[]; return worlds; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les mondes.` : `Unable to retrieve worlds.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all elements for a specific world. * @param userId - The unique identifier of the user * @param worldId - The unique identifier of the world * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of book world elements table records */ static async fetchBookWorldElements(userId: string, worldId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = 'SELECT element_id, world_id, user_id, element_type, name, original_name, description, last_update FROM book_world_elements WHERE user_id=? AND world_id=?'; const params: SQLiteValue[] = [userId, worldId]; const elements: BookWorldElementsTable[] = db.all(query, params) as BookWorldElementsTable[]; return elements; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments du monde.` : `Unable to retrieve world elements.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced worlds for a specific user. * @param userId - The unique identifier of the user * @param lang - The language for error messages ('fr' or 'en') * @returns An array of synced world results */ static fetchSyncedWorlds(userId: string, lang: 'fr' | 'en'): SyncedWorldResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT world_id, book_id, name, last_update FROM book_world WHERE author_id = ?'; const params: SQLiteValue[] = [userId]; const syncedWorlds: SyncedWorldResult[] = db.all(query, params) as SyncedWorldResult[]; return syncedWorlds; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les mondes synchronisés.` : `Unable to retrieve synced worlds.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced world elements for a specific user. * @param userId - The unique identifier of the user * @param lang - The language for error messages ('fr' or 'en') * @returns An array of synced world element results */ static fetchSyncedWorldElements(userId: string, lang: 'fr' | 'en'): SyncedWorldElementResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT element_id, world_id, name, last_update FROM book_world_elements WHERE user_id = ?'; const params: SQLiteValue[] = [userId]; const syncedElements: SyncedWorldElementResult[] = db.all(query, params) as SyncedWorldElementResult[]; return syncedElements; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de monde synchronisés.` : `Unable to retrieve synced world elements.`); } 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 world into the database. * @param worldId - The unique identifier for the world * @param name - The encrypted name of the world * @param hashedName - The hashed name of the world * @param authorId - The unique identifier of the author * @param bookId - The unique identifier of the book * @param history - The encrypted history (optional) * @param politics - The encrypted politics (optional) * @param economy - The encrypted economy (optional) * @param religion - The encrypted religion (optional) * @param languages - The encrypted languages (optional) * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the insertion was successful, false otherwise */ static insertSyncWorld(worldId: string, name: string, hashedName: string, authorId: string, bookId: string, history: string | null, politics: string | null, economy: string | null, religion: string | null, languages: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = `INSERT INTO book_world (world_id, name, hashed_name, author_id, book_id, history, politics, economy, religion, languages, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; const params: SQLiteValue[] = [worldId, name, hashedName, authorId, bookId, history, politics, economy, religion, languages, 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 monde.` : `Unable to insert world.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a synced world element into the database. * @param elementId - The unique identifier for the element * @param worldId - The unique identifier of the parent world * @param userId - The unique identifier of the user * @param elementType - The type of the element * @param name - The encrypted name of the element * @param originalName - The original hashed name * @param description - The encrypted description (optional) * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the insertion was successful, false otherwise */ static insertSyncWorldElement(elementId: string, worldId: string, userId: string, elementType: number, name: string, originalName: string, description: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = `INSERT INTO book_world_elements (element_id, world_id, user_id, element_type, name, original_name, description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`; const params: SQLiteValue[] = [elementId, worldId, userId, elementType, name, originalName, description, 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'élément du monde.` : `Unable to insert world element.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches a complete world by its ID. * @param id - The unique identifier of the world * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of book world table records */ static async fetchCompleteWorldById(id: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = `SELECT world_id, name, hashed_name, author_id, book_id, history, politics, economy, religion, languages, last_update FROM book_world WHERE world_id = ?`; const params: SQLiteValue[] = [id]; const worlds: BookWorldTable[] = db.all(query, params) as BookWorldTable[]; return worlds; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer le monde complet.` : `Unable to retrieve complete world.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches a complete world element by its ID. * @param id - The unique identifier of the element * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of book world elements table records */ static async fetchCompleteWorldElementById(id: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = `SELECT element_id, world_id, user_id, element_type, name, original_name, description, last_update FROM book_world_elements WHERE element_id = ?`; const params: SQLiteValue[] = [id]; const elements: BookWorldElementsTable[] = db.all(query, params) as BookWorldElementsTable[]; return elements; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer l'élément de monde complet.` : `Unable to retrieve complete world element.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates a single world element's name and description. * @param userId - The unique identifier of the user * @param elementId - The unique identifier of the element to update * @param name - The new encrypted name * @param description - The new encrypted description * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update was successful, false otherwise */ static updateWorldElement(userId: string, elementId: string, name: string, description: string, lastUpdate: number, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = `UPDATE book_world_elements SET name = ?, description = ?, last_update = ? WHERE element_id = ? AND user_id = ?`; const params: SQLiteValue[] = [name, description, lastUpdate, elementId, 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 l'élément du monde.` : `Unable to update world element.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if a world exists for a specific user and book. * @param userId - The unique identifier of the user * @param bookId - The unique identifier of the book * @param worldId - The unique identifier of the world * @param lang - The language for error messages ('fr' or 'en') * @returns True if the world exists, false otherwise */ static worldExist(userId: string, bookId: string, worldId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM book_world WHERE world_id=? AND author_id=? AND book_id=?'; const params: SQLiteValue[] = [worldId, userId, bookId]; const world: QueryResult | null = db.get(query, params) || null; return world !== 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 monde.` : `Unable to check world existence.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if a world element exists for a specific user and world. * @param userId - The unique identifier of the user * @param worldId - The unique identifier of the world * @param elementId - The unique identifier of the element * @param lang - The language for error messages ('fr' or 'en') * @returns True if the element exists, false otherwise */ static worldElementExist(userId: string, worldId: string, elementId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM book_world_elements WHERE element_id=? AND world_id=? AND user_id=?'; const params: SQLiteValue[] = [elementId, worldId, userId]; const element: QueryResult | null = db.get(query, params) || null; return element !== 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'élément du monde.` : `Unable to check world element existence.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } }