Files
ERitors-Scribe-Desktop/electron/database/repositories/spell.repo.ts
natreex cec5830360 Introduce series management functionality and repository updates
- Added `series-world.repo.ts` to handle database operations related to series worlds and their elements.
- Implemented `series-sync.repo.ts` for managing synchronization between books and series.
- Expanded `spell.ipc.ts` data models to include `seriesSpellId` for spell synchronization.
- Refactored `insertSpellTag` method in `spelltag.repo.ts` for improved error handling and logic clarity.
2026-01-26 19:57:56 -05:00

379 lines
20 KiB
TypeScript

import { Database, RunResult, SQLiteValue } from 'node-sqlite3-wasm';
import System from '../System.js';
export interface SpellResult extends Record<string, SQLiteValue> {
spell_id: string;
book_id: string;
name: string;
description: string;
appearance: string;
tags: string;
power_level: string | null;
components: string | null;
limitations: string | null;
notes: string | null;
series_spell_id: string | null;
}
export interface BookSpellsTable extends Record<string, SQLiteValue> {
spell_id: string;
book_id: string;
user_id: string;
name: string;
name_hash: string;
description: string;
appearance: string;
tags: string;
power_level: string | null;
components: string | null;
limitations: string | null;
notes: string | null;
last_update: number;
}
export interface SyncedSpellResult extends Record<string, SQLiteValue> {
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, appearance: string, tags: string, 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, appearance: string, tags: string, 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.`);
}
}
}