import { Database, RunResult, SQLiteValue } from 'node-sqlite3-wasm'; import System from '../System.js'; export interface SpellResult extends Record { spell_id: string; book_id: string; name: string; description: string | null; appearance: string | null; tags: string | null; power_level: string | null; components: string | null; limitations: string | null; notes: string | null; series_spell_id: string | null; } export interface BookSpellsTable extends Record { spell_id: string; book_id: string; user_id: string; name: string; name_hash: string; description: string | null; appearance: string | null; tags: string | null; power_level: string | null; components: string | null; limitations: string | null; notes: string | null; last_update: number; } export interface SyncedSpellResult extends Record { spell_id: string; book_id: string; name: string; last_update: number; } export default class SpellRepo { /** * Fetches all spells for a specific book owned by the user. * @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 spell results */ static fetchSpells(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): SpellResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes, series_spell_id FROM book_spells WHERE user_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; return db.all(query, params) as SpellResult[]; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts.` : `Unable to retrieve spells.`); } } /** * Fetches a single spell by its ID. * @param userId - The unique identifier of the user * @param spellId - The unique identifier of the spell * @param lang - The language for error messages ('fr' or 'en') * @returns The spell result or null if not found */ static fetchSpellById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SpellResult | null { try { const db: Database = System.getDb(); const query: string = 'SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes, series_spell_id FROM book_spells WHERE user_id=? AND spell_id=?'; const params: SQLiteValue[] = [userId, spellId]; const spells: SpellResult[] = db.all(query, params) as SpellResult[]; return spells.length > 0 ? spells[0] : null; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de récupérer le sort.` : `Unable to retrieve spell.`); } } /** * Inserts a new spell. * @param spellId - The unique identifier for the new spell * @param bookId - The unique identifier of the book * @param userId - The unique identifier of the user * @param name - The encrypted name * @param nameHash - The hashed name for duplicate detection * @param description - The encrypted description * @param appearance - The encrypted appearance * @param tags - The encrypted JSON tags array * @param powerLevel - The encrypted power level (nullable) * @param components - The encrypted components (nullable) * @param limitations - The encrypted limitations (nullable) * @param notes - The encrypted notes (nullable) * @param lang - The language for error messages ('fr' or 'en') * @returns The spell ID if successful */ static insertSpell(spellId: string, bookId: string, userId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lang: 'fr' | 'en' = 'fr', seriesSpellId: string | null = null): string { let result: RunResult; try { const db: Database = System.getDb(); const query: string = seriesSpellId ? 'INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_spell_id, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)' : 'INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)'; const params: SQLiteValue[] = seriesSpellId ? [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, seriesSpellId, System.timeStampInSeconds()] : [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds()]; result = db.run(query, params); } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible d'ajouter le sort.` : `Unable to add spell.`); } if (!result || result.changes === 0) { throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du sort.` : `Error adding spell.`); } return spellId; } /** * Updates an existing spell. * @param userId - The unique identifier of the user * @param spellId - The unique identifier of the spell * @param name - The encrypted name * @param nameHash - The hashed name * @param description - The encrypted description * @param appearance - The encrypted appearance * @param tags - The encrypted JSON tags array * @param powerLevel - The encrypted power level (nullable) * @param components - The encrypted components (nullable) * @param limitations - The encrypted limitations (nullable) * @param notes - The encrypted notes (nullable) * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update was successful */ static updateSpell(userId: string, spellId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lang: 'fr' | 'en' = 'fr', seriesSpellId: string | null = null): boolean { try { const db: Database = System.getDb(); const query: string = seriesSpellId !== null ? 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, series_spell_id=?, last_update=? WHERE spell_id=? AND user_id=?' : 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, last_update=? WHERE spell_id=? AND user_id=?'; const params: SQLiteValue[] = seriesSpellId !== null ? [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, seriesSpellId, System.timeStampInSeconds(), spellId, userId] : [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds(), spellId, userId]; const result: RunResult = db.run(query, params); return result.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de mettre à jour le sort.` : `Unable to update spell.`); } } /** * Deletes a spell. * @param userId - The unique identifier of the user * @param spellId - The unique identifier of the spell * @param lang - The language for error messages ('fr' or 'en') * @returns True if the deletion was successful */ static deleteSpell(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM book_spells WHERE spell_id=? AND user_id=?'; const params: SQLiteValue[] = [spellId, userId]; const result: RunResult = db.run(query, params); return result.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de supprimer le sort.` : `Unable to delete spell.`); } } /** * Updates the tags field of a spell. * @param userId - The unique identifier of the user * @param spellId - The unique identifier of the spell * @param tags - The new encrypted JSON tags array * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update was successful */ static updateSpellTags(userId: string, spellId: string, tags: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE book_spells SET tags=?, last_update=? WHERE spell_id=? AND user_id=?'; const params: SQLiteValue[] = [tags, System.timeStampInSeconds(), spellId, userId]; const result: RunResult = db.run(query, params); return result.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de mettre à jour les tags du sort.` : `Unable to update spell tags.`); } } /** * Fetches all spells for a book with full table data for sync. * @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 book spells table records */ static fetchBookSpellsTable(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): BookSpellsTable[] { try { const db: Database = System.getDb(); const query: string = 'SELECT spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM book_spells WHERE user_id=? AND book_id=?'; const params: SQLiteValue[] = [userId, bookId]; return db.all(query, params) as BookSpellsTable[]; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts.` : `Unable to retrieve spells.`); } } /** * Fetches a complete spell record by its ID. * @param userId - The unique identifier of the user * @param spellId - The unique identifier of the spell * @param lang - The language for error messages ('fr' or 'en') * @returns The spell table record or null */ static fetchSpellTableById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): BookSpellsTable | null { try { const db: Database = System.getDb(); const query: string = 'SELECT spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM book_spells WHERE user_id=? AND spell_id=?'; const params: SQLiteValue[] = [userId, spellId]; const spells: BookSpellsTable[] = db.all(query, params) as BookSpellsTable[]; return spells.length > 0 ? spells[0] : null; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de récupérer le sort.` : `Unable to retrieve spell.`); } } /** * Fetches all synced spells for a user. * @param userId - The unique identifier of the user * @param lang - The language for error messages ('fr' or 'en') * @returns An array of synced spell results */ static fetchSyncedSpells(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSpellResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT spell_id, book_id, name, last_update FROM book_spells WHERE user_id=?'; const params: SQLiteValue[] = [userId]; return db.all(query, params) as SyncedSpellResult[]; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts synchronisés.` : `Unable to retrieve synced spells.`); } } /** * Inserts or updates a spell from synchronization data. * @param spellId - The unique identifier for the spell * @param bookId - The unique identifier of the book * @param userId - The unique identifier of the user * @param name - The encrypted name * @param nameHash - The hashed name * @param description - The encrypted description * @param appearance - The encrypted appearance * @param tags - The encrypted JSON tags array * @param powerLevel - The encrypted power level (nullable) * @param components - The encrypted components (nullable) * @param limitations - The encrypted limitations (nullable) * @param notes - The encrypted notes (nullable) * @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 */ static insertSyncSpell(spellId: string, bookId: string, userId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string | null, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'INSERT OR REPLACE INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)'; const params: SQLiteValue[] = [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, lastUpdate]; const result: RunResult = db.run(query, params); return result.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible d'insérer le sort.` : `Unable to insert spell.`); } } /** * Checks if a spell exists. * @param userId - The unique identifier of the user * @param spellId - The unique identifier of the spell * @param lang - The language for error messages ('fr' or 'en') * @returns True if the spell exists */ static isSpellExist(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM book_spells WHERE spell_id=? AND user_id=?'; const params: SQLiteValue[] = [spellId, userId]; const existenceCheck = db.all(query, params); return existenceCheck.length > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence du sort.` : `Unable to check spell existence.`); } } /** * Updates a spell with timestamp for sync. * @param userId - The unique identifier of the user * @param spellId - The unique identifier of the spell * @param name - The encrypted name * @param nameHash - The hashed name * @param description - The encrypted description * @param appearance - The encrypted appearance * @param tags - The encrypted JSON tags array * @param powerLevel - The encrypted power level (nullable) * @param components - The encrypted components (nullable) * @param limitations - The encrypted limitations (nullable) * @param notes - The encrypted notes (nullable) * @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 */ static updateSyncSpell(userId: string, spellId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string | null, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, last_update=? WHERE spell_id=? AND user_id=?'; const params: SQLiteValue[] = [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, lastUpdate, spellId, userId]; const result: RunResult = db.run(query, params); return result.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`[SpellRepo] DB Error: ${error.message}`); } else { console.error('[SpellRepo] An unknown error occurred.'); } throw new Error(lang === 'fr' ? `Impossible de mettre à jour le sort.` : `Unable to update spell.`); } } }