- Introduced spell management with creation, editing, deletion, and tagging capabilities. - Added `Spell`, `SpellList`, `SpellTagManager` models with corresponding IPC handlers for data operations. - Implemented `SpellList` and `SpellTagChip` components for UI interactions with spells and tags. - Localized spell-related strings for English (e.g., error messages, tooltips, and prompts). - Enhanced database models and repositories with encryption and decryption workflows for secure data handling. - Updated API to include filtering, searching, and tag-based spell management options.
360 lines
15 KiB
TypeScript
360 lines
15 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';
|
|
|
|
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;
|
|
}
|
|
|
|
export interface SpellListItem {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
tags: SpellTagProps[];
|
|
}
|
|
|
|
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, tagId: string, bookId: string, 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 = System.decryptDataWithUserKey(spell.tags, userKey);
|
|
let tagsArray: string[] = [];
|
|
try {
|
|
tagsArray = 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
|
|
return SpellTagRepo.deleteSpellTag(userId, tagId, lang);
|
|
}
|
|
|
|
/**
|
|
* 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 = System.decryptDataWithUserKey(spell.description, userKey);
|
|
const decryptedTags: string = System.decryptDataWithUserKey(spell.tags, userKey);
|
|
|
|
let tagIds: string[];
|
|
try {
|
|
tagIds = 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.length > 150
|
|
? decryptedDescription.substring(0, 150) + '...'
|
|
: decryptedDescription;
|
|
|
|
return {
|
|
id: spell.spell_id,
|
|
name: decryptedName,
|
|
description: truncatedDescription,
|
|
tags: resolvedTags,
|
|
};
|
|
});
|
|
|
|
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 = System.decryptDataWithUserKey(spell.description, userKey);
|
|
const decryptedAppearance: string = System.decryptDataWithUserKey(spell.appearance, userKey);
|
|
const decryptedTags: string = System.decryptDataWithUserKey(spell.tags, userKey);
|
|
|
|
let tagIds: string[];
|
|
try {
|
|
tagIds = 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,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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'): 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,
|
|
);
|
|
|
|
return {
|
|
id: spellId,
|
|
name,
|
|
description,
|
|
appearance,
|
|
tags,
|
|
powerLevel,
|
|
components,
|
|
limitations,
|
|
notes,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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'): 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,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
return SpellRepo.deleteSpell(userId, spellId, lang);
|
|
}
|
|
}
|