import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm'; import System from "../System.js"; export interface SeriesCharacterResult extends Record { character_id: string; first_name: string; last_name: string; nickname: string | null; age: string | null; gender: string | null; species: string | null; nationality: string | null; status: string | null; title: string; category: string; image: string; role: string; biography: string; history: string; speech_pattern: string | null; catchphrase: string | null; residence: string | null; notes: string | null; color: string | null; } export interface SeriesCharacterAttributeResult extends Record { attr_id: string; attribute_name: string; attribute_value: string; } export interface SeriesCharactersTableResult extends Record { character_id: string; series_id: string; user_id: string; first_name: string; last_name: string | null; nickname: string | null; age: string | null; gender: string | null; species: string | null; nationality: string | null; status: string | null; title: string | null; category: string; image: string | null; role: string | null; biography: string | null; history: string | null; speech_pattern: string | null; catchphrase: string | null; residence: string | null; notes: string | null; color: string | null; last_update: number; } export interface SeriesCharacterAttributesTableResult extends Record { attr_id: string; character_id: string; user_id: string; attribute_name: string; attribute_value: string; last_update: number; } export interface SyncedSeriesCharacterResult extends Record { character_id: string; series_id: string; first_name: string; last_update: number; } export interface SyncedSeriesCharacterAttributeResult extends Record { attr_id: string; character_id: string; attribute_name: string; last_update: number; } export default class SeriesCharacterRepo { /** * Fetches all characters for a specific series owned by the user. * @param userId - The unique identifier of the user * @param seriesId - The unique identifier of the series * @param lang - The language for error messages ('fr' or 'en') * @returns An array of character results */ public static fetchCharacters(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharacterResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT character_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color FROM series_characters WHERE series_id = ? AND user_id = ?'; return db.all(query, [seriesId, userId]) as SeriesCharacterResult[]; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les personnages de la série.` : `Unable to retrieve series characters.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Adds a new character to a series. */ public static addNewCharacter(userId: string, characterId: string, encryptedName: string, encryptedLastName: string | null, encryptedNickname: string | null, encryptedAge: string | null, encryptedGender: string | null, encryptedSpecies: string | null, encryptedNationality: string | null, encryptedStatus: string | null, encryptedTitle: string | null, encryptedCategory: string | null, encryptedImage: string | null, encryptedRole: string | null, encryptedBiography: string | null, encryptedHistory: string | null, encryptedSpeechPattern: string | null, encryptedCatchphrase: string | null, encryptedResidence: string | null, encryptedNotes: string | null, encryptedColor: string | null, seriesId: string, lang: 'fr' | 'en' = 'fr'): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = 'INSERT INTO series_characters (character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; const params: SQLiteValue[] = [characterId, seriesId, userId, encryptedName, encryptedLastName, encryptedNickname, encryptedAge, encryptedGender, encryptedSpecies, encryptedNationality, encryptedStatus, encryptedCategory, encryptedTitle, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, encryptedSpeechPattern, encryptedCatchphrase, encryptedResidence, encryptedNotes, encryptedColor, 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 personnage.` : `Unable to add character.`); } 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 personnage.` : `Error adding character.`); } return characterId; } /** * Inserts a new attribute for a series character. */ static insertAttribute(attributeId: string, characterId: string, userId: string, type: string, name: string, lang: 'fr' | 'en' = 'fr'): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = 'INSERT INTO series_characters_attributes (attr_id, character_id, user_id, attribute_name, attribute_value, last_update) VALUES (?, ?, ?, ?, ?, ?)'; const params: SQLiteValue[] = [attributeId, characterId, userId, type, name, 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'attribut.` : `Unable to add attribute.`); } 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'attribut.` : `Error adding attribute.`); } return attributeId; } /** * Updates an existing series character's information. */ static updateCharacter(userId: string, characterId: string, encryptedName: string, encryptedLastName: string | null, encryptedNickname: string | null, encryptedAge: string | null, encryptedGender: string | null, encryptedSpecies: string | null, encryptedNationality: string | null, encryptedStatus: string | null, encryptedTitle: string | null, encryptedCategory: string | null, encryptedImage: string | null, encryptedRole: string | null, encryptedBiography: string | null, encryptedHistory: string | null, encryptedSpeechPattern: string | null, encryptedCatchphrase: string | null, encryptedResidence: string | null, encryptedNotes: string | null, encryptedColor: string | null, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE series_characters SET first_name = ?, last_name = ?, nickname = ?, age = ?, gender = ?, species = ?, nationality = ?, status = ?, title = ?, category = ?, image = ?, role = ?, biography = ?, history = ?, speech_pattern = ?, catchphrase = ?, residence = ?, notes = ?, color = ?, last_update = ? WHERE character_id = ? AND user_id = ?'; const params: SQLiteValue[] = [encryptedName, encryptedLastName, encryptedNickname, encryptedAge, encryptedGender, encryptedSpecies, encryptedNationality, encryptedStatus, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, encryptedSpeechPattern, encryptedCatchphrase, encryptedResidence, encryptedNotes, encryptedColor, System.timeStampInSeconds(), characterId, 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 le personnage.` : `Unable to update character.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Deletes a series character and all its related data via CASCADE. */ static deleteCharacter(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); // Delete attributes first db.run('DELETE FROM series_characters_attributes WHERE character_id = ? AND user_id = ?', [characterId, userId]); // Delete character const query: string = 'DELETE FROM series_characters WHERE character_id = ? AND user_id = ?'; const deleteResult: RunResult = db.run(query, [characterId, userId]); 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 personnage.` : `Unable to delete character.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Deletes an attribute from a series character. */ static deleteAttribute(userId: string, attributeId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM series_characters_attributes WHERE attr_id = ? AND user_id = ?'; const deleteResult: RunResult = db.run(query, [attributeId, userId]); 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'attribut.` : `Unable to delete attribute.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all attributes for a specific series character. */ static fetchAttributes(characterId: string, userId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharacterAttributeResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT attr_id, attribute_name, attribute_value FROM series_characters_attributes WHERE character_id = ? AND user_id = ?'; const attributes: SeriesCharacterAttributeResult[] = db.all(query, [characterId, userId]) as SeriesCharacterAttributeResult[]; return attributes; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les attributs.` : `Unable to retrieve attributes.`); } 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 series character exists. */ static isCharacterExist(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM series_characters WHERE character_id = ? AND user_id = ?'; const result: QueryResult | null = db.get(query, [characterId, userId]); return result !== 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 personnage.` : `Unable to check character 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 characters for a series for sync. */ static fetchSeriesCharactersTable(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharactersTableResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM series_characters WHERE series_id = ? AND user_id = ?'; const characters: SeriesCharactersTableResult[] = db.all(query, [seriesId, userId]) as SeriesCharactersTableResult[]; return characters; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les personnages pour sync.` : `Unable to retrieve characters for sync.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all attributes for a character for sync. */ static fetchSeriesCharacterAttributesTable(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharacterAttributesTableResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT attr_id, character_id, user_id, attribute_name, attribute_value, last_update FROM series_characters_attributes WHERE character_id = ? AND user_id = ?'; const attributes: SeriesCharacterAttributesTableResult[] = db.all(query, [characterId, userId]) as SeriesCharacterAttributesTableResult[]; return attributes; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les attributs pour sync.` : `Unable to retrieve attributes for sync.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all series characters for a user for sync comparison. */ static fetchSyncedSeriesCharacters(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesCharacterResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT character_id, series_id, first_name, last_update FROM series_characters WHERE user_id = ?'; const characters: SyncedSeriesCharacterResult[] = db.all(query, [userId]) as SyncedSeriesCharacterResult[]; return characters; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les personnages de série pour sync.` : `Unable to retrieve series characters for sync.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all series character attributes for a user for sync comparison. */ static fetchSyncedSeriesCharacterAttributes(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesCharacterAttributeResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT attr_id, character_id, attribute_name, last_update FROM series_characters_attributes WHERE user_id = ?'; const attributes: SyncedSeriesCharacterAttributeResult[] = db.all(query, [userId]) as SyncedSeriesCharacterAttributeResult[]; return attributes; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les attributs de personnage pour sync.` : `Unable to retrieve character attributes for sync.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches a complete character by ID for sync. */ static fetchCompleteCharacterById(characterId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharactersTableResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM series_characters WHERE character_id = ?'; const characters: SeriesCharactersTableResult[] = db.all(query, [characterId]) as SeriesCharactersTableResult[]; return characters; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer le personnage complet.` : `Unable to retrieve complete character.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches a complete character attribute by ID for sync. */ static fetchCompleteAttributeById(attrId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharacterAttributesTableResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT attr_id, character_id, user_id, attribute_name, attribute_value, last_update FROM series_characters_attributes WHERE attr_id = ?'; const attributes: SeriesCharacterAttributesTableResult[] = db.all(query, [attrId]) as SeriesCharacterAttributesTableResult[]; return attributes; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer l'attribut complet.` : `Unable to retrieve complete attribute.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a series character for sync. */ static insertSyncSeriesCharacter(characterId: string, seriesId: string, userId: string, firstName: string, lastName: string | null, nickname: string | null, age: string | null, gender: string | null, species: string | null, nationality: string | null, status: string | null, category: string, title: string | null, image: string | null, role: string | null, biography: string | null, history: string | null, speechPattern: string | null, catchphrase: string | null, residence: string | null, notes: string | null, color: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'INSERT INTO series_characters (character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(character_id) DO UPDATE SET first_name = excluded.first_name, last_name = excluded.last_name, nickname = excluded.nickname, age = excluded.age, gender = excluded.gender, species = excluded.species, nationality = excluded.nationality, status = excluded.status, category = excluded.category, title = excluded.title, image = excluded.image, role = excluded.role, biography = excluded.biography, history = excluded.history, speech_pattern = excluded.speech_pattern, catchphrase = excluded.catchphrase, residence = excluded.residence, notes = excluded.notes, color = excluded.color, last_update = excluded.last_update'; const params: SQLiteValue[] = [characterId, seriesId, userId, firstName, lastName, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speechPattern, catchphrase, residence, notes, color, 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 personnage pour sync.` : `Unable to insert character for sync.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates a series character for sync. */ static updateSyncSeriesCharacter(userId: string, characterId: string, firstName: string, lastName: string | null, nickname: string | null, age: string | null, gender: string | null, species: string | null, nationality: string | null, status: string | null, category: string, title: string | null, image: string | null, role: string | null, biography: string | null, history: string | null, speechPattern: string | null, catchphrase: string | null, residence: string | null, notes: string | null, color: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE series_characters SET first_name = ?, last_name = ?, nickname = ?, age = ?, gender = ?, species = ?, nationality = ?, status = ?, category = ?, title = ?, image = ?, role = ?, biography = ?, history = ?, speech_pattern = ?, catchphrase = ?, residence = ?, notes = ?, color = ?, last_update = ? WHERE character_id = ? AND user_id = ?'; const params: SQLiteValue[] = [firstName, lastName, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speechPattern, catchphrase, residence, notes, color, lastUpdate, characterId, 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 le personnage pour sync.` : `Unable to update character for sync.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a series character attribute for sync. */ static insertSyncSeriesCharacterAttribute(attrId: string, characterId: string, userId: string, attributeName: string, attributeValue: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'INSERT INTO series_characters_attributes (attr_id, character_id, user_id, attribute_name, attribute_value, last_update) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(attr_id) DO UPDATE SET attribute_name = excluded.attribute_name, attribute_value = excluded.attribute_value, last_update = excluded.last_update'; const params: SQLiteValue[] = [attrId, characterId, userId, attributeName, attributeValue, 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'attribut pour sync.` : `Unable to insert attribute for sync.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if a series character attribute exists. */ static isAttributeExist(userId: string, attrId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM series_characters_attributes WHERE attr_id = ? AND user_id = ?'; const result: QueryResult | null = db.get(query, [attrId, userId]); return result !== 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'attribut.` : `Unable to check attribute existence.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates a series character attribute for sync. */ static updateSyncSeriesCharacterAttribute(userId: string, attrId: string, attributeName: string, attributeValue: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE series_characters_attributes SET attribute_name = ?, attribute_value = ?, last_update = ? WHERE attr_id = ? AND user_id = ?'; const params: SQLiteValue[] = [attributeName, attributeValue, lastUpdate, attrId, 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'attribut pour sync.` : `Unable to update attribute for sync.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } }