Files
ERitors-Scribe-Desktop/electron/database/models/Spell.ts
natreex 209dc6f85a Remove CharacterComponent and CharacterDetail components
- Deleted `CharacterComponent` and `CharacterDetail` files from the project.
- Refactored related logic to improve code maintainability and reduce redundancy.
2026-02-05 14:12:08 -05:00

378 lines
16 KiB
TypeScript

import SpellRepo, { SpellResult } from '../repositories/spell.repo.js';
import SpellTagRepo, { SpellTagResult } from '../repositories/spelltag.repo.js';
import BookRepo, { BookToolsTable } from '../repositories/book.repository.js';
import System from '../System.js';
import { getUserEncryptionKey } from '../keyManager.js';
import RemovedItem from './RemovedItem.js';
export interface SpellTagProps {
id: string;
name: string;
color: string | null;
}
export interface SpellProps {
id: string;
name: string;
description: string;
appearance: string;
tags: string[];
powerLevel: string | null;
components: string | null;
limitations: string | null;
notes: string | null;
seriesSpellId: string | null;
}
export interface SpellListItem {
id: string;
name: string;
description: string;
tags: SpellTagProps[];
seriesSpellId?: string | null;
}
export interface SpellListResponse {
enabled: boolean;
spells: SpellListItem[];
tags: SpellTagProps[];
}
export interface SyncedSpell {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSpellTag {
id: string;
name: string;
lastUpdate: number;
}
export default class Spell {
/**
* Retrieves all spell tags for a specific 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 spell tag props
*/
static getSpellTags(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): SpellTagProps[] {
const userKey: string = getUserEncryptionKey(userId);
const spellTags: SpellTagResult[] = SpellTagRepo.fetchSpellTags(userId, bookId, lang);
return spellTags.map((tag: SpellTagResult): SpellTagProps => ({
id: tag.tag_id,
name: System.decryptDataWithUserKey(tag.name, userKey),
color: tag.color,
}));
}
/**
* Adds a new spell tag to a book.
* @param userId - The unique identifier of the user
* @param bookId - The unique identifier of the book
* @param name - The name of the tag
* @param color - The optional color hex code
* @param existingTagId - Optional existing tag ID for sync
* @param lang - The language for error messages ('fr' or 'en')
* @returns The created spell tag props
*/
static addSpellTag(userId: string, bookId: string, name: string, color: string | null, existingTagId?: string, lang: 'fr' | 'en' = 'fr'): SpellTagProps {
const userKey: string = getUserEncryptionKey(userId);
const tagId: string = existingTagId || System.createUniqueId();
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
const nameHash: string = System.hashElement(name);
SpellTagRepo.insertSpellTag(tagId, bookId, userId, encryptedName, nameHash, color, lang);
return {
id: tagId,
name: name,
color: color,
};
}
/**
* Updates an existing spell tag.
* @param userId - The unique identifier of the user
* @param tagId - The unique identifier of the tag
* @param name - The new name of the tag
* @param color - The new optional color hex code
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the update was successful
*/
static updateSpellTag(userId: string, tagId: string, name: string, color: string | null, lang: 'fr' | 'en' = 'fr'): boolean {
const userKey: string = getUserEncryptionKey(userId);
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
const nameHash: string = System.hashElement(name);
return SpellTagRepo.updateSpellTag(userId, tagId, encryptedName, nameHash, color, lang);
}
/**
* Deletes a spell tag and removes its references from all spells in the book.
* @param userId - The unique identifier of the user
* @param tagId - The unique identifier of the tag to delete
* @param bookId - The unique identifier of the book
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the deletion was successful
*/
static deleteSpellTag(userId: string, bookId: string, tagId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
const userKey: string = getUserEncryptionKey(userId);
const spells: SpellResult[] = SpellRepo.fetchSpells(userId, bookId, lang);
for (const spell of spells) {
const decryptedTags: string | null = spell.tags ? System.decryptDataWithUserKey(spell.tags, userKey) : null;
let tagsArray: string[] = [];
try {
tagsArray = decryptedTags ? JSON.parse(decryptedTags) as string[] : [];
} catch {
tagsArray = [];
}
if (tagsArray.includes(tagId)) {
const updatedTags: string[] = tagsArray.filter((t: string): boolean => t !== tagId);
const encryptedTags: string = System.encryptDataWithUserKey(JSON.stringify(updatedTags), userKey);
SpellRepo.updateSpellTags(userId, spell.spell_id, encryptedTags, lang);
}
}
// Then delete the tag
const deleted: boolean = SpellTagRepo.deleteSpellTag(userId, tagId, lang);
if (deleted) {
RemovedItem.deleteTracker(userId, bookId, 'book_spell_tags', tagId, deletedAt, lang);
}
return deleted;
}
/**
* Retrieves the spell list with tags for a specific 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 The spell list response with enabled status, spells, and tags
*/
static getSpellList(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): SpellListResponse {
const userKey: string = getUserEncryptionKey(userId);
const bookTools: BookToolsTable | null = BookRepo.fetchBookTools(userId, bookId, lang);
const enabled: boolean = bookTools ? bookTools.spells_enabled === 1 : false;
const spellTags: SpellTagResult[] = SpellTagRepo.fetchSpellTags(userId, bookId, lang);
const tags: SpellTagProps[] = spellTags.map((tag: SpellTagResult): SpellTagProps => ({
id: tag.tag_id,
name: System.decryptDataWithUserKey(tag.name, userKey),
color: tag.color,
}));
const tagMap: Map<string, SpellTagProps> = new Map();
for (const tag of tags) {
tagMap.set(tag.id, tag);
}
const spellResults: SpellResult[] = SpellRepo.fetchSpells(userId, bookId, lang);
const spells: SpellListItem[] = spellResults.map((spell: SpellResult): SpellListItem => {
const decryptedName: string = System.decryptDataWithUserKey(spell.name, userKey);
const decryptedDescription: string | null = spell.description ? System.decryptDataWithUserKey(spell.description, userKey) : null;
const decryptedTags: string | null = spell.tags ? System.decryptDataWithUserKey(spell.tags, userKey) : null;
let tagIds: string[];
try {
tagIds = decryptedTags ? JSON.parse(decryptedTags) as string[] : [];
} catch {
tagIds = [];
}
const resolvedTags: SpellTagProps[] = tagIds
.map((tagId: string): SpellTagProps | undefined => tagMap.get(tagId))
.filter((tag: SpellTagProps | undefined): tag is SpellTagProps => tag !== undefined);
const truncatedDescription: string = decryptedDescription
? (decryptedDescription.length > 150 ? decryptedDescription.substring(0, 150) + '...' : decryptedDescription)
: '';
return {
id: spell.spell_id,
name: decryptedName,
description: truncatedDescription,
tags: resolvedTags,
seriesSpellId: spell.series_spell_id || null,
};
});
return {
enabled,
spells,
tags,
};
}
/**
* Retrieves the full details of a specific 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 The spell props with all details
*/
static getSpellDetail(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SpellProps {
const userKey: string = getUserEncryptionKey(userId);
const spell: SpellResult | null = SpellRepo.fetchSpellById(userId, spellId, lang);
if (!spell) {
throw new Error(lang === 'fr' ? 'Sort non trouvé.' : 'Spell not found.');
}
const decryptedName: string = System.decryptDataWithUserKey(spell.name, userKey);
const decryptedDescription: string | null = spell.description ? System.decryptDataWithUserKey(spell.description, userKey) : null;
const decryptedAppearance: string | null = spell.appearance ? System.decryptDataWithUserKey(spell.appearance, userKey) : null;
const decryptedTags: string | null = spell.tags ? System.decryptDataWithUserKey(spell.tags, userKey) : null;
let tagIds: string[];
try {
tagIds = decryptedTags ? JSON.parse(decryptedTags) as string[] : [];
} catch {
tagIds = [];
}
return {
id: spell.spell_id,
name: decryptedName,
description: decryptedDescription || '',
appearance: decryptedAppearance || '',
tags: tagIds,
powerLevel: spell.power_level ? System.decryptDataWithUserKey(spell.power_level, userKey) : null,
components: spell.components ? System.decryptDataWithUserKey(spell.components, userKey) : null,
limitations: spell.limitations ? System.decryptDataWithUserKey(spell.limitations, userKey) : null,
notes: spell.notes ? System.decryptDataWithUserKey(spell.notes, userKey) : null,
seriesSpellId: spell.series_spell_id || null,
};
}
/**
* Adds a new spell to a book.
* @param userId - The unique identifier of the user
* @param bookId - The unique identifier of the book
* @param name - The name of the spell
* @param description - The description of the spell
* @param appearance - The appearance of the spell
* @param tags - The tag IDs array
* @param powerLevel - The optional power level
* @param components - The optional components
* @param limitations - The optional limitations
* @param notes - The optional notes
* @param existingSpellId - Optional existing spell ID for sync
* @param lang - The language for error messages ('fr' or 'en')
* @returns The created spell props
*/
static addSpell(userId: string, bookId: string, name: string, description: string, appearance: string, tags: string[], powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, existingSpellId?: string, lang: 'fr' | 'en' = 'fr', seriesSpellId: string | null = null): SpellProps {
const userKey: string = getUserEncryptionKey(userId);
const spellId: string = existingSpellId || System.createUniqueId();
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
const nameHash: string = System.hashElement(name);
const encryptedDescription: string = System.encryptDataWithUserKey(description, userKey);
const encryptedAppearance: string = System.encryptDataWithUserKey(appearance, userKey);
const encryptedTags: string = System.encryptDataWithUserKey(JSON.stringify(tags), userKey);
const encryptedPowerLevel: string | null = powerLevel ? System.encryptDataWithUserKey(powerLevel, userKey) : null;
const encryptedComponents: string | null = components ? System.encryptDataWithUserKey(components, userKey) : null;
const encryptedLimitations: string | null = limitations ? System.encryptDataWithUserKey(limitations, userKey) : null;
const encryptedNotes: string | null = notes ? System.encryptDataWithUserKey(notes, userKey) : null;
SpellRepo.insertSpell(
spellId,
bookId,
userId,
encryptedName,
nameHash,
encryptedDescription,
encryptedAppearance,
encryptedTags,
encryptedPowerLevel,
encryptedComponents,
encryptedLimitations,
encryptedNotes,
lang,
seriesSpellId,
);
return {
id: spellId,
name,
description,
appearance,
tags,
powerLevel,
components,
limitations,
notes,
seriesSpellId,
};
}
/**
* Updates an existing spell.
* @param userId - The unique identifier of the user
* @param spellId - The unique identifier of the spell
* @param name - The name of the spell
* @param description - The description of the spell
* @param appearance - The appearance of the spell
* @param tags - The tag IDs array
* @param powerLevel - The optional power level
* @param components - The optional components
* @param limitations - The optional limitations
* @param notes - The optional notes
* @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, description: string, appearance: string, tags: string[], powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lang: 'fr' | 'en' = 'fr', seriesSpellId: string | null = null): boolean {
const userKey: string = getUserEncryptionKey(userId);
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
const nameHash: string = System.hashElement(name);
const encryptedDescription: string = System.encryptDataWithUserKey(description, userKey);
const encryptedAppearance: string = System.encryptDataWithUserKey(appearance, userKey);
const encryptedTags: string = System.encryptDataWithUserKey(JSON.stringify(tags), userKey);
const encryptedPowerLevel: string | null = powerLevel ? System.encryptDataWithUserKey(powerLevel, userKey) : null;
const encryptedComponents: string | null = components ? System.encryptDataWithUserKey(components, userKey) : null;
const encryptedLimitations: string | null = limitations ? System.encryptDataWithUserKey(limitations, userKey) : null;
const encryptedNotes: string | null = notes ? System.encryptDataWithUserKey(notes, userKey) : null;
return SpellRepo.updateSpell(
userId,
spellId,
encryptedName,
nameHash,
encryptedDescription,
encryptedAppearance,
encryptedTags,
encryptedPowerLevel,
encryptedComponents,
encryptedLimitations,
encryptedNotes,
lang,
seriesSpellId,
);
}
/**
* Deletes a spell.
* @param userId - The unique identifier of the user
* @param bookId - The unique identifier of the book
* @param spellId - The unique identifier of the spell
* @param deletedAt - The timestamp of deletion
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the deletion was successful
*/
static deleteSpell(userId: string, bookId: string, spellId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
const deleted: boolean = SpellRepo.deleteSpell(userId, spellId, lang);
if (deleted) {
RemovedItem.deleteTracker(userId, bookId, 'book_spells', spellId, deletedAt, lang);
}
return deleted;
}
}